RabbitMQ的消息确认机制,消息重试机制

承接上文SpringBoot整合RabbitMQ

RabbitMQ的消息确认机制

RabbitMQ的消息确认有两种。

一种是消息发送确认。这种是用来确认生产者将消息发送给交换器,交换器传递给队列的过程中,消息是否成功投递。发送确认分为两步,一是确认是否到达交换器,二是确认是否到达队列。

第二种是消费接收确认。这种是确认消费者是否成功消费了队列中的消息。

消息发送确认
通过实现ConfirmCallBack接口,消息发送到交换器Exchange后触发回调。
通过实现ReturnCallback接口,如果消息从交换器发送到对应队列失败时触发(比如根据发送消息时指定的routingKey找不到队列时会触发)

producer模块添加如下配置

spring:
  rabbitmq:
    publisher-confirms: true #confirm回调
    publisher-returns: true #return回调
@Configuration
public class RabbitConfig {

    private final Logger logger = LoggerFactory.getLogger(RabbitConfig.class);

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);

        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            //成功发送到交换机会触发该回调
            if (ack){
                //表示消息发送成功,这里不做任何操作,这里只是为了写个注释(手动狗头)
            }else {
                //表示消息成功发送到服务器,但是没有找到交换器,这里可以记录日志,方便后续处理
                logger.warn("ConfirmCallback -> 消息发布到交换器失败,错误原因为:{}", cause);
            }
        });

        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            //表示消息发送到交换机,但是没有找到队列,这里记录日志
            logger.warn("ReturnCallback -> 消息{},发送到队列失败,应答码:{},原因:{},交换器: {},路由键:{}",
                    message,
                    replyCode,
                    replyText,
                    exchange,
                    routingKey);
        });

        return rabbitTemplate;
    }
}

消息接收确认
AcknowledgeMode.NONE:不确认,不会发送任何ack
AcknowledgeMode.AUTO:自动确认,就是自动发送ack,除非抛异常
AcknowledgeMode.MANUAL:手动确认,手动发送ack
ACK这里有很多坑,推荐看这篇博客RabbitMQ的ack或nack机制使用不当导致的队列堵塞或死循环问题

一般都会使用手动确认,下面在consumer模块添加配置

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual #手动应答

改造接收端代码,消费消息成功之后进行手动ack

@RabbitListener(queues = Constants.DEFAULT_DIRECT_QUEUE)
public void directConsumer(String msg, Channel channel,Message message) {
    System.out.println("直连型交换机收到消息:"+msg);
    //确认消息,false表示不批量处理
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}

考虑消息消费失败,继续改造接收端代码

@RabbitListener(queues = Constants.DEFAULT_DIRECT_QUEUE)
public void directConsumer(String msg, Channel channel,Message message) throws IOException {
    try{
    	//这里表示消息消费成功
        System.out.println("直连型交换机收到消息:"+msg);
        //确认消息,false表示不批量处理
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }catch (Exception e){
		//这里表示消息消费失败,当消息消费失败时,如果没有进行处理,会导致MQ中Unacked的消息越来越多,最终占用内存越来越大
		
        //方案一:返回NACK,并重新放入队列,这种情况可能会导致死循环
//            channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true);

        //方案二,这里也返回ACK,记录报错日志,后续根据日志进行恢复处理
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        logger.error("消息消费失败:{}", e.getMessage());
    }
}

RabbitMQ的消息重试机制

consumer模块添加配置

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true #是否开启重试
          initial-interval: 3000ms #重试时间间隔
          max-attempts: 3 #重试次数
          max-interval: 15000ms #重试最大时间间隔
          multiplier: 2 #倍数

这里改造一下Topic主题交换机绑定的队列

@RabbitListener(queues = Constants.MAN_TOPIC_QUEUE)
    public void topicManConsumer(String msg, Channel channel,Message message) throws IOException {
        if ("error".equals(msg)){
            //这里抛出检查其期异常不会进行重试,抛出检查期异常时重试多少次还是会抛出异常
            //消息被拒绝之后不会进行重试
            //抛出运行时异常会进行重试
            System.out.println("消息消费失败");
            throw new RuntimeException("业务出错");
        }
        System.out.println("主题交换机收到消息-man:"+msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
    }

发送端发送字符串error,会发现成功触发消息重试
第一条是另一个监听队列total 收到的消息
RabbitMQ的消息确认机制,消息重试机制_第1张图片

项目代码:项目代码
相关阅读:
RabbitMQ入门
SpringBoot整合RabbitMQ
RabbitMQ的死信队列与延时队列

你可能感兴趣的:(RabbitMQ)