RabbitMQ入门教程(十二):消息确认Ack
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。
分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。
一:消费者确认
消费者确认或者说消费者应答指的是RabbitMQ需要确认消息到底有没有被收到
- 自动应答
boolean autoAck = true;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
- 1
- 2
在订阅消息的时候可以指定应答模式,当自动应答等于true的时候,表示当消费者一收到消息就表示消费者收到了消息,消费者收到了消息就会立即从队列中删除。
生产者
public class Producer {
@Test
public void testBasicPublish() throws IOException, TimeoutException, InterruptedException {
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("/");
factory.setHost("127.0.0.1");
factory.setPort(AMQP.PROTOCOL.PORT);
factory.setUsername("mengday");
factory.setPassword("mengday");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
String EXCHANGE_NAME = "exchange.direct";
String QUEUE_NAME = "queue_name";
String ROUTING_KEY = "key";
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);
String message = "Hello RabbitMQ:";
for (int i = 0; i < 5; i++) {
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, (message + i).getBytes("UTF-8"));
}
channel.close();
connection.close();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
消费者
public class Consumer1 {
@Test
public void testBasicConsumer1() throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("/");
factory.setHost("127.0.0.1");
factory.setPort(AMQP.PROTOCOL.PORT); // 5672
factory.setUsername("mengday");
factory.setPassword("mengday");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
String EXCHANGE_NAME = "exchange.direct";
String QUEUE_NAME = "queue_name";
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key");
// GetResponse response = channel.basicGet(QUEUE_NAME, false);
// byte[] body = response.getBody();
// System.out.println(new String(body).toString());
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
Thread.sleep(100000);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
运行结果:
运行生产者可以看到Ready=5, Unacked=0, Total=5, Total代表队列中的消息总条数,Ready代表消费者还可以读到的条数,Unacked:代表还有多少条没有被应答
在消费者端的获取消息的第一行打个断点,可以看到,第一次进入到handleDelivery()方法时,队列瞬间被清空。Ready=0, Unacked=0, Total=0
当消费者连接上队列了,因为没有指定消费者一次获取消息的条数,所以队列把队列中的所有消息一下子推送到消费者端,当消费者订阅的该队列,消息就会从队列推到客户端,当消息从队列被推出的时的那一刻就表示已经对消息进行自动确认了,消息就会从队列中删除。
- 手动应答
手动应答和自动应答不一样,需要将autoAck设置为false,当消费者收到消息在合适的时候来显示的进行确认,说我已经接收到了该消息了,RabbitMQ可以从队列中删除该消息了,可以通过显示调用channel.basicAck(envelope.getDeliveryTag(), false);来告诉消息服务器来删除消息
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, consumer);
- 1
- 2
消费者
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
Thread.sleep(100000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
当代码执行完channel.basicConsume(QUEUE_NAME, false, consumer);还没有进入到handleDelivery()方法时可以看到Ready=0, Unacked=5, Total=5
当代码进入handleDelivery()方法没执行一次channel.basicAck(envelope.getDeliveryTag(), false);Unacked和Total就会减去1,直到两个值都为0
特殊情况:手动应答如果忘记写channel.basicAck(envelope.getDeliveryTag(), false)这行代码,现象是消费者仍然能获取所有消息,不过此时Unacked和Total一直都是5,Ready=0, Unacked=5, Total=5,直到消费者运行结束,Ready=5, Unacked=0, Total=5
特殊情况2:如果设置消费者每次从队列中获取指定的条数channel.basicQos(1);,此时如果没有应答的话,消费者将不再继续获取
// 因设置了一次获取一条,所以可读的为4,未应答的是1
// 继续运行,因为一次只获取一条,而这一条还没有应答,就没有办法继续获取下一条
// 消费者运行结束的时候又回到原来的状态Ready=5, Unacked=0, Total=5
注意:如果都没有手动应答,在没有指定获取消息的条数时,消费者可以获取所有消息,当指定时,只能获取指定条,下次就只能等待了,没法继续获取下一条了
- 手动拒绝
手动应答是除了确认应答,也可以拒绝应答。
requeue=true,表示将消息重新放入到队列中,false:表示直接从队列中删除,此时和basicAck(long deliveryTag, false)的效果一样
void basicReject(long deliveryTag, boolean requeue);
- 1
- 2
消费者代码示例一:
public class Consumer1 {
@Test
public void testBasicConsumer1() throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost("/");
factory.setHost("127.0.0.1");
factory.setPort(AMQP.PROTOCOL.PORT); // 5672
factory.setUsername("mengday");
factory.setPassword("mengday");
Connection connection = factory.newConnection();
final Channel channel = connection.createChannel();
String EXCHANGE_NAME = "exchange.direct";
String QUEUE_NAME = "queue_name";
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "key");
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
if (message.contains(":3")){
// requeue:重新入队列,false:直接丢弃,相当于告诉队列可以直接删除掉
channel.basicReject(envelope.getDeliveryTag(), false);
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
Thread.sleep(100000);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
消费者代码示例二:
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
if (message.contains(":3")){
// requeue:重新入队列,true: 重新放入队列
channel.basicReject(envelope.getDeliveryTag(), true);
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
Thread.sleep(100000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
结果解释:代码中没有指定设定消费者一次从队列中获取消息的条数,所以消费者一下子拿到了5条消息,消费了0、1、2当消费第i=3时执行channel.basicReject(envelope.getDeliveryTag(), true);会将消息放入到队列中,然后将消息推送给消费者, 然后消费4,接着再消费3,还会再次放入到队列,整个过程死循环,Ready=0, Unacked=1, Total=1, 当消费者运行结束了,Ready=1, Unacked=0, Total=1, 这个1就是消息3
- 重新投递
basicRecover(): 重新投递并没有所谓的像basicReject中的basicReject的deliveryTag参数,所以重新投递好像是将消费者还没有处理的所有的消息都重新放入到队列中,而不是将某一条消息放入到队列中,与basicReject不同的是,重新投递可以指定投递的消息是否允许当前消费者消费。
// If true, messages will be requeued and possibly delivered to a different consumer. If false, messages will be redelivered to the same consumer.
Basic.RecoverOk basicRecover(boolean requeue);
- 1
- 2
- 3
示例代码一:
Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(message);
if (message.contains(":3")){
channel.basicRecover(true);
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
};
channel.basicConsume(QUEUE_NAME, false, consumer);
Thread.sleep(100000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
这里不太明白,true的话表示会被其他消费者消费,不知道3、4又被接收了一次???
false:表示重新递送的消息还会被当前消费者消费
二:生产者确认
当生产者发布消息到RabbitMQ中,生产者需要知道是否真的已经发送到RabbitMQ中,需要RabbitMQ告诉生产者。
Confirm机制
channel.confirmSelect();
channel.waitForConfirms();事务机制
channel.txSelect();
channel.txRollback();
注意:事务机制是非常非常非常消耗性能的,最好使用Confirm机制,Confirm机制相比事务机制性能上要好很多。
channel.confirmSelect();
String message = "Hello RabbitMQ:";
for (int i = 0; i < 5; i++) {
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, (message + i).getBytes("UTF-8"));
}
boolean isAllPublished = channel.waitForConfirms();
- 1
- 2
- 3
- 4
- 5
- 6
String message = "Hello RabbitMQ:";
try {
channel.txSelect();
for (int i = 0; i < 5; i++) {
channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, (message + i).getBytes("UTF-8"));
}
channel.txCommit();
} catch (Exception e) {
channel.txRollback();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。
我的微信公众号:
RabbitMQ入门教程(十二):消息确认Ack的更多相关文章
- RabbitMQ入门教程(十四):RabbitMQ单机集群搭建
原文:RabbitMQ入门教程(十四):RabbitMQ单机集群搭建 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...
- RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较
原文:RabbitMQ入门教程(十七):消息队列的应用场景和常见的消息队列之间的比较 分享一个朋友的人工智能教程.比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看. 这是网上的一篇教程写的很好,不知原作 ...
- RabbitMQ入门教程(十六):RabbitMQ与Spring集成
原文:RabbitMQ入门教程(十六):RabbitMQ与Spring集成 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https: ...
- RabbitMQ入门教程(十):队列声明queueDeclare
原文:RabbitMQ入门教程(十):队列声明queueDeclare 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...
- RabbitMQ入门教程(十五):普通集群和镜像集群
原文:RabbitMQ入门教程(十五):普通集群和镜像集群 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.c ...
- 无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two]
无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two] extjs技术交流,欢迎加群(201926085) 不管是几级下拉列表的联动实现本质上都是根据某个下拉列表的变化,去动态加载其 ...
- RabbitMQ入门教程(十一):消息属性Properties
原文:RabbitMQ入门教程(十一):消息属性Properties 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://b ...
- RabbitMQ (十二) 消息确认机制 - 发布者确认
消费者确认解决的问题是确认消息是否被消费者"成功消费". 它有个前提条件,那就是生产者发布的消息已经"成功"发送出去了. 因此还需要一个机制来告诉生产者,你发送 ...
- RabbitMQ入门教程(十):队列声明queueDeclare(转载)
原文转载至:https://blog.csdn.net/vbirdbest/article/details/78670550 简介本节主要讨论队列声明的各个参数 queueDeclare(String ...
随机推荐
- HDU 5818 Joint Stacks (优先队列)
Joint Stacks 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5818 Description A stack is a data stru ...
- Spark指标项监控
监控配置 spark的监控主要分为Master.Worker.driver.executor监控.Master和Worker的监控在spark集群运行时即可监控,Driver和Excutor的监控需要 ...
- [BZOJ1001][BeiJing2006]狼抓兔子(最小割转最短路|平面图转对偶图)
1001: [BeiJing2006]狼抓兔子 Time Limit: 15 Sec Memory Limit: 162 MBSubmit: 31805 Solved: 8494[Submit][ ...
- Eclipse常用快捷键与IDEA中的对比.
最近从github下载了一些项目,但是看了一下使用的编译器是IDEA的,所以就下载了一个IDEA. 这边可以提供几个网址:只要是针对各个下载idea之后的一些激活相关的帮助. http://idea. ...
- docker运行jpress
1.前提安装好docker mkdir docker//建立工作文件夹 cd docker 2.下载tomcat镜像和jpress开源项目 docker pull docker pull hub.c. ...
- 第九周总结&实验报告七
小结:这周请了一天的假,所以回来的时候有些知识点跟不上,不过在第二节课学到了关于IO的知识很重要,对于这次的实验也有些吃力,这周的知识点主要集中在书上,在各种不同条件下学习运用什么样的代码. 一. ...
- Docker—备份、恢复及迁移
用容器生成镜像 [root@git docker]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e950a988d ...
- CentOS 6.4 搭建 ntop 网络流量监控分析平台
[前言] Ntop是一种监控网络流量工具,用ntop显示网络的使用情况比其他一些网络管理软件更加直观.详细.Ntop甚至可以列出每个节点计算机的网络带宽利用率. 功能: 自动从网络中识别有用的信息: ...
- 利用docker启动 wordpress
网上有很多教程哈,我只是记录自己怎么玩的,没啥教学意义 查看镜像说明的mysql/data目录,方便挂载 [root@docker ~]# docker inspect -f {{.Config.Vo ...
- leetcode886可能的二分法
class Solution { public: bool possibleBipartition(int N, vector<vector<int>>& dislik ...