A. Delivery Tag

参考资料:https://www.rabbitmq.com/confirms.html

仔细查看一下 Consumer 的回调方法:

            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
......
consumerChannel1.basicAck(envelope.getDeliveryTag(), false);
}

当我们需要确认一条消息已经被消费时,我们调用的 basicAck 方法的第一个参数是 Delivery Tag。

Delivery Tag 用来标识信道中投递的消息。RabbitMQ 推送消息给 Consumer 时,会附带一个 Delivery Tag,以便 Consumer 可以在消息确认时告诉 RabbitMQ 到底是哪条消息被确认了。

RabbitMQ 保证在每个信道中,每条消息的 Delivery Tag 从 1 开始递增。

运行下面的例子可以直观的看到这点:

gordon.study.rabbitmq.ack.TestAckBasic.java

public class TestAckBasic {

    private static final String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
final Channel consumerChannel1 = connection.createChannel();
consumerChannel1.queueDeclare(QUEUE_NAME, false, false, false, null);
consumerChannel1.basicQos(3);
Consumer consumer1 = new DefaultConsumer(consumerChannel1) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.printf("in consumer A (delivery tag is %d): %s\n", envelope.getDeliveryTag(), message);
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
}
consumerChannel1.basicAck(envelope.getDeliveryTag(), false);
}
};
consumerChannel1.basicConsume(QUEUE_NAME, false, consumer1); final Channel consumerChannel2 = connection.createChannel();
consumerChannel2.basicQos(3);
Consumer consumer2 = new DefaultConsumer(consumerChannel2) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
System.out.printf("in consumer B (delivery tag is %d): %s\n", envelope.getDeliveryTag(), message);
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
}
consumerChannel2.basicAck(envelope.getDeliveryTag(), false);
}
};
consumerChannel2.basicConsume(QUEUE_NAME, false, consumer2); Channel senderChannel = connection.createChannel();
for (int i = 0; i < 10;) {
String message = "NO. " + ++i;
TimeUnit.MILLISECONDS.sleep(100);
senderChannel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
}
senderChannel.close();
}
}

result:

in consumer A (delivery tag is 1): NO. 1
in consumer B (delivery tag is 1): NO. 2
in consumer A (delivery tag is 2): NO. 3
in consumer B (delivery tag is 2): NO. 4
in consumer A (delivery tag is 3): NO. 5
in consumer B (delivery tag is 3): NO. 6
in consumer A (delivery tag is 4): NO. 7
in consumer B (delivery tag is 4): NO. 8
in consumer A (delivery tag is 5): NO. 9
in consumer B (delivery tag is 5): NO. 10

可见,两个信道的 delivery tag 分别从 1 递增到 5。(如果修改代码,将两个 Consumer 共享同一个信道,则 delivery tag 是从 1 递增到 10,参考 gordon.study.rabbitmq.ack.TestAckInOneChannel.java

basicAck 方法的第二个参数 multiple 取值为 false 时,表示通知 RabbitMQ 当前消息被确认;如果为 true,则额外将比第一个参数指定的 delivery tag 小的消息一并确认。(批量确认针对的是整个信道,参考gordon.study.rabbitmq.ack.TestBatchAckInOneChannel.java。)

对同一消息的重复确认,或者对不存在的消息的确认,会产生 IO 异常,导致信道关闭。

B. 忘了确认会怎样

如果我们注释掉22行,让 consumerChannel1 不再确认消息,世界会怎样?

只要程序还在运行,这3条消息就一直是 Unacked 状态,无法被 RabbitMQ 重新投递。更厉害的是,RabbitMQ 消息消费并没有超时机制,也就是说,程序不重启,消息就永远是 Unacked 状态。处理运维事件时不要忘了这些 Unacked 状态的消息

当程序关闭时(实际只要 Consumer 关闭就行),这3条消息会恢复为 Ready 状态。

C. 取消确认

当消费消息出现异常时,我们需要取消确认,这时我们可以使用 Channel 的 basicReject 方法。

    void basicReject(long deliveryTag, boolean requeue) throws IOException;

第一个参数指定 delivery tag,第二个参数说明如何处理这个失败消息。requeue 值为 true 表示该消息重新放回队列头,值为 false 表示放弃这条消息。

一般来说,如果是系统无法处理的异常,我们一般是将 requeue 设为 false,例如消息格式错误,再处理多少次也是异常。调用第三方接口超时这类异常 requeue 应该设为 true。

从 basicReject 方法参数可见,取消确认不支持批量操作(类似于 basicAck 的 multiple 参数)。所以,RabbitMQ 增加了 basicNack 方法以提供批量取消能力。参考 https://www.rabbitmq.com/nack.html

PS:Reject 的消息重新推送来时,delivery tag 就是新的值了。

RabbitMQ入门_06_深入了解ack的更多相关文章

  1. RabbitMQ入门教程(十二):消息确认Ack

    原文:RabbitMQ入门教程(十二):消息确认Ack 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csd ...

  2. 2.RABBITMQ 入门 - WINDOWS - 生产和消费消息 一个完整案例

    关于安装和配置,见上一篇 1.RABBITMQ 入门 - WINDOWS - 获取,安装,配置 公司有需求,要求使用winform开发这个东西(消息中间件),另外还要求开发一个日志中间件,但是也是要求 ...

  3. [转]RabbitMQ入门教程(概念,应用场景,安装,使用)

    原文地址:https://www.jianshu.com/p/dae5bbed39b1 RabbitMQ 简介 RabbitMQ是一个在AMQP(Advanced Message Queuing Pr ...

  4. .NET 环境中使用RabbitMQ RabbitMQ与Redis队列对比 RabbitMQ入门与使用篇

    .NET 环境中使用RabbitMQ   在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的 ...

  5. 消息中间件 RabbitMQ 入门篇

    消息中间件 RabbitMQ 入门篇 五月君 K8S中文社区 今天   作者:五月君,来源:Nodejs技术栈 从不浪费时间的人,没有工夫抱怨时间不够.—— 杰弗逊 RabbitMQ 是一套开源(MP ...

  6. rabbitmq监控之消息确认ack

    rabbitmq springboot ack 监控 一.测试环境 二.启动测试 一直以来,学习rabbitmq都是跟着各种各样的教程.博客.视频和文档,撸起袖子就是干!!!最后,也成功了. 当然,成 ...

  7. RabbitMQ入门(二)工作队列

      在文章RabbitMQ入门(一)之Hello World,我们编写程序通过指定的队列来发送和接受消息.在本文中,我们将会创建工作队列(Work Queue),通过多个workers来分配耗时任务. ...

  8. RabbitMQ入门案例

    RabbitMQ入门案例 Rabbit 模式 https://www.rabbitmq.com/getstarted.html 实现步骤 构建一个 maven工程 导入 rabbitmq的依赖 启动 ...

  9. RabbitMQ入门到进阶(Spring整合RabbitMQ&SpringBoot整合RabbitMQ)

    1.MQ简介 MQ 全称为 Message Queue,是在消息的传输过程中保存消息的容器.多用于分布式系统 之间进行通信. 2.为什么要用 MQ 1.流量消峰 没使用MQ 使用了MQ 2.应用解耦 ...

随机推荐

  1. 《Kalchbrenner N, Grefenstette E, Blunsom P. A convolutional neural network for modelling sentences》

    Kalchbrenner’s Paper Kal的这篇文章引用次数较高,他提出了一种名为DCNN(Dynamic Convolutional Neural Network)的网络模型,在上一篇(Kim ...

  2. linux安装composer及安装yii2

    wget https://getcomposer.org/download/1.3.2/composer.phar mv composer.phar /usr/local/bin/composer c ...

  3. marquee实现跑马灯

    <!DOCTYPE html><html> <head><title>跑马灯大全</title> </head> <bod ...

  4. Trove系列(八)——Trove的配置管理相关的功能介绍

    概述MySQL 配置管理功能允许Trove 用户重载由Trove服务的操作者提供的缺省MySQL配置环境.这是通过影响MySQL 的includedir 命令来实现的.这些MySQL 的include ...

  5. Python: yield, python 实现tail -f

    def CreateGenerator(file): with open(file,'r') as t: t.seek(0,2) while True: line=t.readline() if no ...

  6. C/C++之进制转换

    二进制.八进制.十进制.十六进制之间转换 一. 十进制与二进制之间的转换  (1) 十进制转换为二进制,分为整数部分和小数部分  ① 整数部分  方法:除2取余法,即每次将整数部分除以2,余数为该位权 ...

  7. where T : class含义

    .NET支持的类型参数约束有以下五种: where T : struct                               | T必须是一个结构类型where T : class       ...

  8. 怎样将一个整数转化成字符串数,并且不用函数itoa

    #include<iostream> using namespace std; int main() { , j = , i = ; ], str[]; while (num) { tem ...

  9. Kafka学习之(五)搭建kafka集群之Zookeeper集群搭建

    Zookeeper是一种在分布式系统中被广泛用来作为:分布式状态管理.分布式协调管理.分布式配置管理.和分布式锁服务的集群.kafka增加和减少服务器都会在Zookeeper节点上触发相应的事件kaf ...

  10. nginx配置https并强制http自动跳转到https

    关于使用HTTPS/SSL的必要性,可以自行baidu,援引的说法,EFF(Electronic Frontier Foundation),全球过半流量采用https. https://www.osc ...