RabbitMQ学习笔记(二)消息确认机制

前言

1、我的另一篇文章中介绍了RabbitMQ的交换机类型、消费类型,经过一系列的demo测试时,我们在RabbitMQ的后台监控中可以发现一个现象,就是消息在被成功消费后,队列中就没有这个消息了,也就是删除了,那么疑问来了,RabbitMQ是怎么知道这个消息该不该被删除?什么时候删除?
2、除了消费者,那么生产者怎么知道自己消息是否发送成功到RabbitMQ中了?

解决这个疑问的方式就是:RabbitMQ消息确认机制

RabbitMQ有两种解决消息丢失的方案,一种是事务机制,一种是消息确认(confirm机制),这里主要介绍消息确认

1、为什么要进行消息确认

在这里插入图片描述

通过RabbitMQ的原理图可知,生产者和消费者并没有直接进行通信,中间要使用RabbitMQ传递消息,所以生产者只需要把消息发送到Rabbit,而消费者只需要从队列获取消息就可以了

  • 生产者:生产者发布消息后,并不知道消息发送成功没,但是生产者需要知道,所以这里会有一个消息确认机制,帮助生产者确认消息发送成功
  • 消费者:消费者拿到消息后,在队列中这个消息还在,队列并不知道怎么处理这个消息,是删除还是死信,所以队列需要消费者给一个ACK确认,也就是消费者处理完消息后的反馈,队列根据反馈来选择处理方式

上述两种就是消息确认机制

二、消息确认的基本逻辑(源于官网)

RabbitMQ是基于AMQP协议的,从官网给出的解释来说,会有两种建议:

  • 当消息代理(broker)将消息发送给应用后立即删除。(使用AMQP方法:basic.deliver或basic.get-ok)
  • 待应用(application)发送一个确认回执(acknowledgement)后再删除消息。(使用AMQP方法:basic.ack)

前者被称作自动确认模式(automatic acknowledgement model),后者被称作显式确认模式(explicit acknowledgement model)。在显式模式下,由消费者应用来选择什么时候发送确认回执(acknowledgement)。应用可以在收到消息后立即发送,或将未处理的消息存储后发送,或等到消息被处理完毕后再发送确认回执(例如,成功获取一个网页内容并将其存储之后)。

如果一个消费者在尚未发送确认回执的情况下挂掉了,那AMQP代理会将消息重新投递给另一个消费者。如果当时没有可用的消费者了,消息代理会等下一个注册到此队列的消费者,然后再次尝试投递。

三、生产者确认

生产者端可通过两个callback接口来确认消息的是否传递到Broker,但是两个接口默认都是不开启的

3.1 ConfirmCallback方法

ConfirmCallback方法:消息成功从生产者到Broker(RabbitMQ主机)后触发的回调,只用来确认是否正确到达了Exchange(交换机),发布者确认默认是不开启的,开启发布者确认需要添加如下配置

// springboot配置
spring.rabbitmq.publisher-confirm-type=correlated # 新版本
spring.rabbitmq.publisher-confirms=true # 老版本

3.2 ReturnCallback方法

ReturnCallback方法:消息从Exchange到queue投递失败,则会触发的回调,但是这个方法被触发的概率很小,因为交换机和队列的绑定是在代码中显式完成的,只要代码正确这个方法出发的概率就会很小,即便触发了也大概率是代码的问题,而这个方法也需要通过配置来开启

// springboot配置
spring.rabbitmq.publisher-returns=true

以上两种配置适用于springboot项目,如果没有使用springboot的话可以使用如下代码来进行配置

  • 几种方法都在channel类中
    在这里插入图片描述
    在这里插入图片描述

四、消费者确认(重点)

消费者确认有两种方式:自动、手动

4.1 消息自动确认

消息自动确认是指消息发出后就认为消息消费成功,消息就会被RabbitMQ从队列中删除掉,并不会在意消费者处理业务的成功与否,也就是“发送即成功”,这种模式是一种非常不安全的方式,因为业务存在处理失败的情况,这样的话数据就会丢失
如下代码中的第二个参数,就是设定为自动确认模式(autoAck:true)
在这里插入图片描述

4.2 消息手动确认

手动应答提供三种接口

  • basic.ack(deliveryTag,multiple) 用来确认成功消息(positive acknowledgements)
  • basic.nack(deliveryTag,requeue,multiple) 用来确认失败的消息(negative acknowledgements)
  • basic.reject(deliveryTa,requeue) 用来确认失败的消息

4.2.1 成功确认:basic.ack(deliveryTag,multiple)

  • deliveryTag:消息传递标签,格式为序列号,必须使用这个标签,不然信道会关闭,详情下面会说到
  • multiple:为true则表示序号deliverTag之前的消息均被确认或拒绝(basicNack),false表示当前消息。为true的时候就可以做到批量确认
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);

4.2.2 失败确认:basic.nack(deliveryTag,multiple,requeue)

  • requeue:为true表示,失败的消息将会重新排队,不会丢弃或者死信(下篇文章描述),为false则表示丢弃
channel.basicNack(message.getEnvelope().getDeliveryTag(),false, true);

4.2.3 失败确认:basic.basicReject(deliveryTag,requeue)

相比nack方法,reject没有multiple参数,所以reject只能处理单个消息

channel.basicReject(message.getEnvelope().getDeliveryTag(), true);

4.3 消息传递标签(delivery tags)

RabbitMQ在通过队列以及信道(channel)传递消息给消费者的时候,都会带一个传递标签,每个channel给每条消息分配一个标签,一个channel对应一个传递标签。
这个标签是一个Long类型的序列号,从1 开始到 9223372036854775807,理论上这个范围非常大,按照每秒一百万的数据量来算的话,大概需要292年才能用完。
通过Delivery.getEnvelope().getDeliveryTag()可以获取当前消息的传递标签
在这里插入图片描述
代码示例中查看这个传递标签的值为22,这表示这个消息是这个channel传递的第22条
在这里插入图片描述
RabbitMQ就是通过传递标签确认消费者返回的是哪条消息的结果,从而进行处理

4.3.1 如果使用错误的传递标签

这里是指,在做消息确认的时候,并没有传递正确的传递标签,那么会出现什么情况?
经过测试如下

  • 生产者代码:
public class Producer {

    public static String QUEUE_NAME = "q_test_01";

    public static void main(String[] args) throws Exception {
        Connection connection = getConnection();
        Channel channel = connection.createChannel();
        String msg = "测试消息一";
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", QUEUE_NAME, null, (msg + i).getBytes());
        }


        //关闭通道和连接
        channel.close();
        connection.close();
    }

    public static Connection getConnection() throws Exception {
        //定义连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //设置服务地址
        factory.setHost("localhost");
        //端口
        factory.setPort(5672);
        //设置账号信息,用户名、密码、vhost
        factory.setVirtualHost("testhost");
        factory.setUsername("admin");
        factory.setPassword("1111");
        // 通过工程获取连接
        Connection connection = factory.newConnection();
        return connection;
    }
}
  • 消费者代码:注意代码中的channel.basicAck
public class ConsumerClient {
    public static void main(String[] args) throws Exception {
        Connection connection = Producer.getConnection();
        Channel channel = connection.createChannel();

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String message1 = new String(message.getBody());
            System.out.println("DeliverCallback:" + consumerTag + "-" + message1);
            // 注意这里message.getEnvelope().getDeliveryTag()就是获取正确的传递标签
            // 然后我在后面 +9999 改变正确的值
            channel.basicAck(message.getEnvelope().getDeliveryTag()+99999, false);
        };

        CancelCallback cancelCallback = consumerTag -> {
            System.out.println("CancelCallback:" + consumerTag);
        };

        channel.basicConsume(Producer.QUEUE_NAME, false, deliverCallback, cancelCallback);
    }
}
  • 结果如下:
    • 生产者启动后:队列成功创建,消息成功发送,通过RabbitMQ后台可以看到队列q_test_01中有10个消息
    • 消费者启动:成功获取到10条消息,在控制台可以正确的输出打印
      在这里插入图片描述
      我们回到RabbitMQ的后台中看一下,如下图
      在这里插入图片描述
      可以发现队列中的消息依然存在,并没有删除掉,我们在手动消息确认的时候,没有使用正确的传递标签,RabbitMQ根据错误的传递标签是找不到消息的,那我们将传递标签修改为正确的,再来看结果
      在这里插入图片描述
      在这里插入图片描述
      启动后,我们发现之前消费过10条消息,又被重新消费了,而且回到RabbitMQ后台可以发现队列中的消息已经被删除了。
      消息被重新消费是因为之前使用了错误的传递标签,原有的消息没有被删除,这样就相当于消息没有被消费过,并且处于待消费的一个状态,那么在消费者重新启动连接后,队列会把原有的消息直接发送给新连接的消费者,这个新的消费者就是我们正确使用了传递标签的消费者,所以消息被正确的确认,队列就将其删除
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值