rabbitmq 消息确认

287 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、生产者消息确认

image-20200928090540942.png

步骤:

  1. 创建spring-confirm-mq项目

    • web/lombok/mq
  2. 初始化队列和交换机信息

  3. 编写配置文件

  4. 发送消息测试

解决办法:

  • 第一步消息没有正确到达交换机-----》confirm确认模式解决
  • 第二步消息没有正确的路由至队列----》return退回模式解决

解决步骤:

  1. 在配置文件中开启确认模式、退回模式
  2. 编写类实现RabbitTemplate.ConfirmCallBack、RabbitTemplate.ReturnCallBack接口并且重写方法
  3. 将重写的方法引用到RabbitTemplate中
1. 代码实现
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
​
import javax.annotation.PostConstruct;
​
/**
 * <dl>
 * <dd>描述: ~ RabbitMqConfirm</dd>
 * <dd>创建时间:  9:31 2020/9/28</dd>
 * <dd>创建人: guodong</dd>
 * <dt>版本历史: </dt>
 * <pre>
 * Date         Author         Version     Description
 * ------------------------------------------------------------------
 * </pre>
 * </dl>
 */
@Component
@Slf4j
public class RabbitMqConfirm implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
​
    @Autowired
    private RabbitTemplate rabbitTemplate;
​
    @PostConstruct //会在@Autowired后执行
    public void init(){
        rabbitTemplate.setConfirmCallback(this::confirm);
        rabbitTemplate.setReturnCallback(this::returnedMessage);
    }
​
​
    /**
     * @Date: 9:33 2020/9/28
     * @Parms [correlationData, ack, cause]
     * @ReturnType: void
     * @Description: 确认消息是否可以到达交换机
    */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            log.info("消息正确到达交换机, ack {}", ack);
        }else {
            log.info("消息到达交换机失败, ack {}, cause:{}", ack, cause);
        }
    }
​
    /**
     * @Date: 9:34 2020/9/28
     * @Parms [message, replyCode, replyText, exchange, routingKey]
     * @ReturnType: void
     * @Description: 只有消息从交换机路由至队列失败时会调用
    */
    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        log.error("消息从交换机路由至队列失败: replyCode: {}, replyText: {} exchange: {} routingKey:{} ",
                replyCode, replyText, exchange, routingKey);
    }
}
spring:
  rabbitmq:
    host: 192.168.200.132
    #开启确认模式
    publisher-confirms: true
    #开启退回模式
    publisher-returns: true

二、消费消息确认

1. ack确认三种模式:
  • manual: 手动确认

    • 需要手动确认消息可以删除还是重新回到队列
  • auto: 自动确认

    • 消息一旦消费异常,那么消息会自动重回队列进行排队等待消费,如果大批量消息消费失败会造成消息的积压,从而影响消息正常消费
  • none: 不需要确认

    • 不需要确认,一旦消费者接收到消息后自动将消息从队列删除
2. 手动确认模式代码实现

步骤:

  1. 在配置文件中开启手动确认
  2. 修改消费消费业务逻辑
3. 代码实现
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
​
import java.io.IOException;
​
/**
 * <dl>
 * <dd>描述: ~ OrderAListener</dd>
 * <dd>创建时间:  9:21 2020/9/28</dd>
 * <dd>创建人: </dd>
 * <dt>版本历史: </dt>
 * <pre>
 * Date         Author         Version     Description
 * ------------------------------------------------------------------
 * 2020/9/28      guodong         1.0        1.0 Version
 * </pre>
 * </dl>
 */
@RabbitListener(queues = "order.A")
@Component
@Slf4j
public class OrderAListener {
​
    @RabbitHandler
    public void receiveMsg(String msg,
                           Channel channel,
                           @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag,
                           @Header(AmqpHeaders.REDELIVERED) boolean redelivered){
        log.info("orderA接收到消息: {}, deliveryTag: {}, redelivered: {}", msg, deliveryTag, redelivered);
        try {
            if(msg.contains("苹果")){
                throw new RuntimeException("爱国就不要买苹果手机!!!");
            }
            /*
                第一个参数是: 消息的位置
                第二个参数是: 是否要批量确认,如果设置了批量确认那么在进行消息确认删除时会将小于
                               该消息位置的消息全部删除,造成别的消费逻辑的变化。所以一般设置为false
             */
            channel.basicAck(deliveryTag, false);
        }catch (Exception e){
            if(redelivered){ //如果参数为true标识重回过队列
                try {
                    /*
                        channel.basicReject: 单条消息拒绝,第一个参数是消息的位置,第二个参数是标识是否重写回到队列
                        channel.basicNack: 多了一个可以批量拒绝的参数,但是一般不使用批量,所以使用basicReject就可以。
                     */
                    channel.basicReject(deliveryTag, false);
                    //channel.basicNack(deliveryTag, false, false);
                    log.info("消息已经重新回到过队列并且再次消费失败,拒绝重新再次回到队列, redelivered: {}", redelivered);
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }else { //第一次消费失败,没有重新回到过队列,应该给一次机会
                try {
                    channel.basicReject(deliveryTag, true);
                    log.info("消息第一次消费失败,重新回到队列, redelivered: {}", redelivered);
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
}
​
spring:
  rabbitmq:
    host: 192.168.200.132
    #开启确认模式
    publisher-confirms: true
    #开启退回模式
    publisher-returns: true
    listener:
      simple:
        acknowledge-mode: manual #手动确认 auto #自动确认 none #不需要确认

4.小结
  • 在配置文件中开启消息的手动确认

  • 在消费者里面编写业务逻辑代码

    • Channel channel : 用于进行消息的确认

    • @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag: 消息的位置

    • @Header(AmqpHeaders.REDELIVERED) boolean redelivered: 标识消息是否重回过队列,如果重新回到过那么为true

    • 用于手动进行消息确认的方法

      • 参数介绍

        • deliveryTag: 消息的位置
        • multiple: 是否批量确认,批量确认会造成小于该消息的消息被确认删除
        • requeue: 是否重回队列
      • channel.basicAck(long deliveryTag, boolean multiple)

      • channel.basicNack(long deliveryTag, boolean multiple, boolean requeue)

      • channel.basicReject(long deliveryTag, boolean requeue)