RabbitMQ - Consumer Ack

126 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情

ack - acknowledge,在 RabbitMQ 中指代消费者收到消息后确认的行为,能够反映消费者是否接收到MQ的消息并成功消费

确认方式

目前提供了三种确认方式:

  • acknowledge="none":当消费者接收到消息后,自动给到 MQ 一个回执,没有后续消费者业务逻辑是否成功。
  • acknowledge="manual":消费者接收到消息后,处理业务逻辑,通过调用代码的方式告诉MQ 消息是否消费成功,如果失败,MQ会再次发送一遍消息,做一些重试机制。
  • acknowledge="auto":通过抛出异常的类型来做响应的处理(比如重发、确认等等)

如果设置了手动确认消息是否接收成功的方式,需要手动调用 channel.basicAck(),手动的签收。如果业务处理失败,手动调用channel.basicNack() 方法拒收,并让MQ重新发送该消息。

如果不做任何关于acknowledge的配置,默认就是自动确认签收的

代码示例

pom文件

spring:
  rabbitmq:
    addresses: xx.xx.xx.xxx
    username: xxx
    password: xxxx
    port: 5672
    listener:
      simple:
        # 关键
        acknowledge-mode: manual
        retry:
          enabled: true
          # 重试次数
          max-attempts: 5
          # 重试最大间隔时间
          max-interval: 10000
          # 重试初始间隔时间
          initial-interval: 2000
          # 间隔时间乘子,间隔时间*乘子=下一次的间隔时间,最大不能超过设置的最大间隔时间
          multiplier: 2

config

@Configuration
public class RabbitMQConfig {
    public static final String TEST_ACK = "test.ack";
    @Bean
    public Queue getQueue(){
       return new Queue(COUNT_STATISTICS_QUEUE);
    }
}

生产者

@Order
@Component
@Slf4j
public class HelloSender {
    @Autowired
    private AmqpTemplate rabbitTemplate;
​
    public void testAck() {
//        //消费者接收到该消息,解析到true,就模拟调用channel.basicAck确认签收消息
//        this.rabbitTemplate.convertAndSend(RabbitMQConfig.TEST_ACK, "test msg send [true]");
        //消费者接收到该消息,解析到false,就模拟调用channel.basicNack,拒收消息,让MQ重发
        rabbitTemplate.convertAndSend(RabbitMQConfig.TEST_ACK, "test msg send [false]");
    }
}

消费者

@Component
public class AckListener implements ChannelAwareMessageListener {
​
    @RabbitListener(queuesToDeclare = @Queue(value = RabbitMQConfig.TEST_ACK))
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        Thread.sleep(1000);
        boolean tag = new String(message.getBody()).contains("true");
        System.out.println("接收到msg:" + new String(message.getBody()));
        //获取mes deliveryTag
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
​
        try {
            if (tag) {
                System.out.println("业务处理成功");
                //手动签收
                /*
                 * deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
                 * multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
                 */
                channel.basicAck(deliveryTag, false);
            } else {
                //模拟业务处理失败抛出异常
                System.out.println("业务处理失败");
                throw new IOException("业务处理失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
            /*
             * deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
             * multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
             * requeue: true该条消息重新返回MQ queue,MQ broker将会重新发送该条消息
             */
            channel.basicNack(deliveryTag, false, true);
            //也可以使用channel.basicReject(deliveryTag, requeue),它只能拒收单条消息
            //channel.basicReject(deliveryTag, true);
        }
    }
}

使用 queuesToDeclare 别使用 queues ,这样第一次可以自动创建队列,避免报错