springboot中rabbitmq实现消息可靠性机制详解


Posted in Java/Android onSeptember 25, 2021

1. 生产者模块通过publisher confirm机制实现消息可靠性

 1.1 生产者模块导入rabbitmq相关依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--用于mq消息的序列化与反序列化-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

1.2 配置文件中进行mq的相关配置

spring.rabbitmq.host=10.128.240.183
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
        
spring.rabbitmq.publisher-confirm-type=correlated
        
spring.rabbitmq.publisher-returns=true
        
spring.rabbitmq.template.mandatory=true
  • publish-confirm-type:开启publisher-confirm,有以下可选值

simple:同步等待confirm结果,直到超时
correlated:异步回调,定义ConfirmCallback。mq返回结果时会回调这个ConfirmCallback

  • publish-returns:开启publish-return功能。可以定义ReturnCallback
  • template.mandatory: 定义消息路由失败的策略

true:调用ReturnCallback
false:直接丢弃消息

1.3 定义ReturnCallback(消息投递到队列失败触发此回调)

  • 每个RabbitTemplate只能配置一个ReturnCallback。
  • 当消息投递失败,就会调用生产者的returnCallback中定义的处理逻辑
  • 可以在容器启动时就配置这个回调
@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取RabbitTemplate对象
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 配置ReturnCallback
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            // 判断是否是延迟消息
            Integer receivedDelay = message.getMessageProperties().getReceivedDelay();
            if (receivedDelay != null && receivedDelay > 0) {
                // 是一个延迟消息,忽略这个错误提示
                return;
            }
            // 记录日志
            log.error("消息发送到队列失败,响应码:{}, 失败原因:{}, 交换机: {}, 路由key:{}, 消息: {}",
                     replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有需要的话,重发消息
        });
    }
}

1.4 定义ConfirmCallback(消息到达交换机触发此回调)

可以为redisTemplate指定一个统一的确认回调

@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取RabbitTemplate对象
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 配置ReturnCallback
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            // 判断是否是延迟消息
            Integer receivedDelay = message.getMessageProperties().getReceivedDelay();
            if (receivedDelay != null && receivedDelay > 0) {
                // 是一个延迟消息,忽略这个错误提示
                return;
            }
            // 记录日志
            log.error("消息发送到队列失败,响应码:{}, 失败原因:{}, 交换机: {}, 路由key:{}, 消息: {}",
                     replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有需要的话,重发消息
        });
 
        
        // 设置统一的confirm回调。只要消息到达broker就ack=true
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                System.out.println("这是统一的回调");
                System.out.println("correlationData:" + correlationData);
                System.out.println("ack:" + b);
                System.out.println("cause:" + s);
            }
        });
    }
}

也可以为特定的消息定制回调

@Autowired
    private RabbitTemplate rabbitTemplate;
 
    @Test
    public void testmq() throws InterruptedException {
 
 
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
 
        correlationData.getFuture().addCallback(result->{
            if (result.isAck()) {
                // ACK
                log.debug("消息成功投递到交换机!消息ID: {}", correlationData.getId());
            } else {
                // NACK
                log.error("消息投递到交换机失败!消息ID:{}", correlationData.getId());
                // 重发消息
            }
        },ex->{
            // 记录日志
            log.error("消息发送失败!", ex);
            // 重发消息
        });
        rabbitTemplate.convertAndSend("example.direct","blue","hello,world",correlationData);
    }

2. 消费者模块开启消息确认

2.1 添加配置

# 手动ack消息,不使用默认的消费端确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
  • none:关闭ack,消息投递时不可靠的,可能丢失
  • auto:类似事务机制,出现异常时返回nack,消息回滚到mq,没有异常,返回
  • ackmanual:我们自己指定什么时候返回ack

2.2 manual模式在监听器中自定义返回ack

@RabbitListener(queues = "order.release.order.queue")
@Service
public class OrderCloseListener {
 
    @Autowired
    private OrderService orderService;
 
 
    @RabbitHandler
    private void listener(OrderEntity orderEntity, Channel channel, Message message) throws IOException {
        System.out.println("收到过期的订单信息,准备关闭订单" + orderEntity.getOrderSn());
 
        try {
            orderService.closeOrder(orderEntity);
            // 第二个参数为false则表示仅确认此条消息。如果为true则表示对收到的多条消息同时确认
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            // 第二个参数为ture表示将这个消息重新加入队列
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }
}

3. 消费者模块开启消息失败重试机制

3.1 配置文件添加配置,开启本地重试

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 1000 # 初识的失败等待时长为1秒
          multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3 # 最大重试次数
          stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
  • 开启本地重试,如果消息处理过程总抛出异常,不会requeue到队列,而是在消费者本地重试
  • 重试达到最大次数后,spring会返回ack,消息会被丢弃

4.  消费者模块添加失败策略(用于开启失败本地重试功能后)

  • 当开启本地重试后,重试最大次数后消息直接丢弃。
  • 三种策略,都继承于MessageRecovery接口
  • RejectAndDontRequeueRecoverer:重试耗尽后,直接reject,丢弃消息。默认就是这种方式
  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队
  • RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

4.2 定义处理失败消息的交换机和队列 没有会自动创建相应的队列、交换机与绑定关系,有了就啥也不做

@Bean
public DirectExchange errorMessageExchange(){
    return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){
    return new Queue("error.queue", true);
}
 
// 路由键为key
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
    return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
}

4.3 向容器中添加一个失败策略组件

@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
    // error为路由键
    return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}

到此这篇关于springboot中rabbitmq实现消息可靠性的文章就介绍到这了,更多相关springboot rabbitmq消息可靠性内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Java/Android 相关文章推荐
SpringCloud Alibaba 基本开发框架搭建过程
Jun 13 Java/Android
解决Maven项目中 Invalid bound statement 无效的绑定问题
Jun 15 Java/Android
解决SpringCloud Feign传对象参数调用失败的问题
Jun 23 Java/Android
ObjectMapper 如何忽略字段大小写
Jun 29 Java/Android
总结Java对象被序列化的两种方法
Jun 30 Java/Android
Java 泛型详解(超详细的java泛型方法解析)
Jul 02 Java/Android
SpringBoot工程下使用OpenFeign的坑及解决
Jul 02 Java/Android
详细了解java监听器和过滤器
Jul 09 Java/Android
关于MybatisPlus配置双数据库驱动连接数据库问题
Jan 22 Java/Android
Java GUI编程菜单组件实例详解
Apr 07 Java/Android
Java 数组的使用
May 11 Java/Android
Java数据结构之堆(优先队列)
May 20 Java/Android
Spring Cloud 中@FeignClient注解中的contextId属性详解
Sep 25 #Java/Android
关于springboot配置druid数据源不生效问题(踩坑记)
Sep 25 #Java/Android
Java使用Unsafe类的示例详解
Sep 25 #Java/Android
Spring-cloud Config Server的3种配置方式
Sep 25 #Java/Android
MyBatis-Plus 批量插入数据的操作方法
Sep 25 #Java/Android
spring cloud 配置中心native配置方式
Sep 25 #Java/Android
spring cloud 配置中心客户端启动遇到的问题
Sep 25 #Java/Android
You might like
PHP exif扩展方法开启详解
2014/07/28 PHP
PHPStrom中实用的功能和快捷键大全
2015/09/23 PHP
关于laravel 数据库迁移中integer类型是无法指定长度的问题
2019/10/09 PHP
纯CSS打造的导航菜单(附jquery版)
2010/08/07 Javascript
jquery显示隐藏input对象
2014/07/21 Javascript
jquery获得同源iframe内body下标签的值的方法
2014/09/25 Javascript
浅谈JSON中stringify 函数、toJosn函数和parse函数
2015/01/26 Javascript
jQuery打字效果实现方法(附demo源码下载)
2015/12/18 Javascript
JS组件系列之Bootstrap Icon图标选择组件
2016/01/28 Javascript
Bootstrap中datetimepicker使用小结
2016/12/28 Javascript
bootstrap——bootstrapTable实现隐藏列的示例
2017/01/14 Javascript
简单实现js选项卡切换效果
2017/02/09 Javascript
浅谈jquery拼接字符串效率比较高的方法
2017/02/22 Javascript
使用JavaScript进行表单校验功能
2017/08/01 Javascript
Vue中的异步组件函数实现代码
2018/07/20 Javascript
vue 点击按钮增加一行的方法
2018/09/07 Javascript
对angularJs中$sce服务安全显示html文本的实例
2018/09/30 Javascript
[38:42]完美世界DOTA2联赛循环赛 Matador vs Forest BO2第二场 11.05
2020/11/05 DOTA
python3实现全角和半角字符转换的方法示例
2017/09/21 Python
Python爬虫信息输入及页面的切换方法
2018/05/11 Python
python爬虫 爬取58同城上所有城市的租房信息详解
2019/07/30 Python
python对csv文件追加写入列的方法
2019/08/01 Python
Python使用Socket实现简单聊天程序
2020/02/28 Python
6种非常炫酷的CSS3按钮边框动画特效
2016/03/16 HTML / CSS
HTML5如何实现元素拖拽
2016/03/11 HTML / CSS
台湾时尚彩瞳专门店:imeime
2019/08/16 全球购物
英国美术用品购物网站:Cass Art
2019/10/08 全球购物
2014年向国旗敬礼活动方案
2014/09/27 职场文书
党的群众路线教育实践活动个人对照检查材料(四风)
2014/11/05 职场文书
社会实践活动总结
2015/02/05 职场文书
投资合作意向书范本
2015/05/08 职场文书
《成长的天空》读后感3篇
2019/12/06 职场文书
使用Python的开发框架Brownie部署以太坊智能合约
2021/05/28 Python
MySql 8.0及对应驱动包匹配的注意点说明
2021/06/23 MySQL
vue项目proxyTable配置和部署服务器
2022/04/14 Vue.js
Mybatis-plus配置分页插件返回统一结果集
2022/06/21 Java/Android