RabbitMQ:消息发送确认 与 消息接收确认(ACK)
默认情况下如果一个 Message 被消费者所正确接收则会被从 Queue 中移除
如果一个 Queue 没被任何消费者订阅,那么这个 Queue 中的消息会被 Cache(缓存),当有消费者订阅时则会立即发送,当 Message 被消费者正确接收时,就会被从 Queue 中移除
消息发送确认
发送的消息怎么样才算失败或成功?如何确认?
- 当消息无法路由到队列时,确认消息路由失败。消息成功路由时,当需要发送的队列都发送成功后,进行确认消息,对于持久化队列意味着写入磁盘,对于镜像队列意味着所有镜像接收成功
消息接收确认
消息消费者如何通知 Rabbit 消息消费成功?
- 消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK
- 自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
- 如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
- 如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者
- 如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限
- ACK 机制还可以起到限流作用,比如在接收到某条消息时休眠几秒钟
- 消息确认模式有:
- AcknowledgeMode.NONE:自动确认
- AcknowledgeMode.AUTO:根据情况确认
- AcknowledgeMode.MANUAL:手动确认
确认消息(局部方法处理消息)
- 默认情况下消息消费者是自动 ack (确认)消息的,如果要手动 ack(确认)则需要修改确认模式为 manual
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
- 或在 RabbitListenerContainerFactory 中进行开启手动 ack
@Bean
public RabbitListenerContainerFactory<?> rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); //开启手动 ack
return factory;
}
- 确认消息
@RabbitHandler
public void processMessage2(String message,Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
System.out.println(message);
try {
channel.basicAck(tag,false); // 确认消息
} catch (IOException e) {
e.printStackTrace();
}
}
- 需要注意的 basicAck 方法需要传递两个参数
- deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
- multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
手动否认、拒绝消息
- 发送一个 header 中包含 error 属性的消息

- 消费者获取消息时检查到头部包含 error 则 nack 消息
@RabbitHandler
public void processMessage2(String message, Channel channel,@Headers Map<String,Object> map) {
System.out.println(message);
if (map.get("error")!= null){
System.out.println("错误的消息");
try {
channel.basicNack((Long)map.get(AmqpHeaders.DELIVERY_TAG),false,true); //否认消息
return;
} catch (IOException e) {
e.printStackTrace();
}
}
try {
channel.basicAck((Long)map.get(AmqpHeaders.DELIVERY_TAG),false); //确认消息
} catch (IOException e) {
e.printStackTrace();
}
}
- 此时控制台重复打印,说明该消息被 nack 后一直重新入队列然后一直重新消费
hello
错误的消息
hello
错误的消息
hello
错误的消息
hello
错误的消息
- 也可以拒绝该消息,消息会被丢弃,不会重回队列
channel.basicReject((Long)map.get(AmqpHeaders.DELIVERY_TAG),false); //拒绝消息
确认消息 三种类型(全局处理消息)
- 自动确认 NONE(涉及到一个问题就是如果在处理消息的时候抛出异常,消息处理失败,但是因为自动确认而导致 Rabbit 将该消息删除了,造成消息丢失)
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("consumer_queue"); // 监听的队列
container.setAcknowledgeMode(AcknowledgeMode.NONE); // NONE 代表自动确认
container.setMessageListener((MessageListener) message -> { //消息监听处理
System.out.println("====接收到消息=====");
System.out.println(new String(message.getBody()));
//相当于自己的一些消费逻辑抛错误
throw new NullPointerException("consumer fail");
});
return container;
}
- 手动确认消息 MANUAL
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("consumer_queue"); // 监听的队列
container.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 手动确认
container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> { //消息处理
System.out.println("====接收到消息=====");
System.out.println(new String(message.getBody()));
if(message.getMessageProperties().getHeaders().get("error") == null){
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("消息已经确认");
}else {
//channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
System.out.println("消息拒绝");
} });
return container;
}
- 根据情况确认消息 AUTO
- AcknowledgeMode 除了 NONE 和 MANUAL 之外还有 AUTO ,它会根据方法的执行情况来决定是否确认还是拒绝(是否重新入queue)
如果消息成功被消费(成功的意思是在消费的过程中没有抛出异常),则自动确认
当抛出 AmqpRejectAndDontRequeueException 异常的时候,则消息会被拒绝,且 requeue = false(不重新入队列)
当抛出 ImmediateAcknowledgeAmqpException 异常,则消费者会被确认
其他的异常,则消息会被拒绝,且 requeue = true(如果此时只有一个消费者监听该队列,则有发生死循环的风险,多消费端也会造成资源的极大浪费,这个在开发过程中一定要避免的)。可以通过 setDefaultRequeueRejected(默认是true)去设置
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory){
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("consumer_queue"); // 监听的队列
container.setAcknowledgeMode(AcknowledgeMode.AUTO); // 根据情况确认消息
container.setMessageListener((MessageListener) (message) -> {
System.out.println("====接收到消息=====");
System.out.println(new String(message.getBody()));
//抛出NullPointerException异常则重新入队列
//throw new NullPointerException("消息消费失败");
//当抛出的异常是AmqpRejectAndDontRequeueException异常的时候,则消息会被拒绝,且requeue=false
//throw new AmqpRejectAndDontRequeueException("消息消费失败");
//当抛出ImmediateAcknowledgeAmqpException异常,则消费者会被确认
throw new ImmediateAcknowledgeAmqpException("消息消费失败");
});
return container;
}
消息可靠总结
- 持久化
- exchange要持久化
- queue要持久化
- message要持久化
- 消息确认
- 启动消费返回(@ReturnList注解,生产者就可以知道哪些消息没有发出去)
- 生产者和Server(broker)之间的消息确认
- 消费者和Server(broker)之间的消息确认
原文:https://www.jianshu.com/p/2c5eebfd0e95
RabbitMQ:消息发送确认 与 消息接收确认(ACK)的更多相关文章
- SpringCloud(六) - RabbitMQ安装,三种消息发送模式,消息发送确认,消息消费确认(自动,手动)
1.安装erlang语言环境 1.1 创建 erlang安装目录 mkdir erlang 1.2 上传解压压缩包 上传到: /root/ 解压缩# tar -zxvf otp_src_22.0.ta ...
- rabbitmq消息队列,消息发送失败,消息持久化,消费者处理失败相关
转:https://blog.csdn.net/u014373554/article/details/92686063 项目是使用springboot项目开发的,前是代码实现,后面有分析发送消息失败. ...
- RabbitMQ消息确认(发送确认,接收确认)
前面几篇记录了收发消息的demo,今天记录下关于 消息确认方面的 问题. 下面是几个问题: 1.为什么要进行消息确认? 2.rabbitmq消息确认 机制是什么样的? 3.发送方如何确认消息发送成功? ...
- 一张图进阶 RocketMQ - 消息发送
前 言 三此君看了好几本书,看了很多遍源码整理的 一张图进阶 RocketMQ 图片链接,关于 RocketMQ 你只需要记住这张图!觉得不错的话,记得点赞关注哦. [重要]视频在 B 站同步更新,欢 ...
- RocketMQ 消息发送system busy、broker busy原因分析与解决方案
目录 1.现象 2.原理解读 2.1 RocketMQ 网络处理机制概述 2.2 pair.getObject1().rejectRequest() 2.3 漫谈transientStorePoolE ...
- 钉钉机器人SDK 封装预警消息发送工具
1 群机器人 (1) 引言 钉钉聊天群内支持的群机器人, 类似QQ 群机器人, 可以发天气, 讲笑话那样; 钉钉群机器人支持自定义机器人, 允许开发者管理机器人做预警消息通知; ...
- 源码分析 Kafka 消息发送流程(文末附流程图)
温馨提示:本文基于 Kafka 2.2.1 版本.本文主要是以源码的手段一步一步探究消息发送流程,如果对源码不感兴趣,可以直接跳到文末查看消息发送流程图与消息发送本地缓存存储结构. 从上文 初识 Ka ...
- 【RocketMQ】MQ消息发送
消息发送 首先来看一个RcoketMQ发送消息的例子: @Service public class MQService { @Autowired DefaultMQProducer defaultMQ ...
- kafka 保证消息被消费和消息只消费一次
1. 保证消息被消费 即使消息发送到了消息队列,消息也不会万无一失,还是会面临丢失的风险. 我们以 Kafka 为例,消息在Kafka 中是存储在本地磁盘上的, 为了减少消息存储对磁盘的随机 I/O, ...
随机推荐
- Pycharm的远程代码编辑
作为一个从java转到python的程序猿,一直觉得python的远程代码调试能力不如java,远程调试一把需要各种改代码,牵扯到eventlet库的时候,问题就更严重,需要调整eventlet的各种 ...
- zabbix的配置
一.网络自动发现: 1.zabbix的网络自动发现是一个非常强大的功能,该功能可以完成以下工作. a.快速发现并添加主机. b.简单的管理. c.随着环境的改变而快速搭建监控系统. 2.网络发现基于以 ...
- Codeforces 1107G Vasya and Maximum Profit 线段树最大子段和 + 单调栈
Codeforces 1107G 线段树最大子段和 + 单调栈 G. Vasya and Maximum Profit Description: Vasya got really tired of t ...
- mysql数据库int(5)以及varchar(20)长度表示的是什么?
在mysql5.x版本的数据库中: int类型数据的字节大小是固定的4个字节: 但是int(5)和int(11)区别在于,显示的数据位数一个是5位一个是11位,在开启zerofill(填充零)情况下, ...
- 决策树算法原理及JAVA实现(ID3)
0 引言 决策树的目的在于构造一颗树像下面这样的树. 图1 图2 1. 如何构造呢? 1.1 参考资料. 本例以图2为例,并参考了以下资料. (1) http://www.cnblog ...
- pig flatten
今天通过不断的尝试,终于知道这个flatten的用法了.其实吧,有时候关键是要test,才能充分理解解说.不过,同事给说的有点问题,误导了我.整的我一直没明白怎么回事. 这是官方的解释: The FL ...
- 17、SAM文件格式说明(转载迷宫中的将军)
1. SAM格式说明 SAM代表Sequence Alignment/Map格式,是一种制表符分隔的文本格式,包含一个可选的头部分(header section,有人称之为“注释部分”),和一个比对部 ...
- ASCII 说明
ASCII 说明 ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符.标准 ASCII 码也叫基础ASCII码,使用 7 位二进制数来表示所有的大写和小写字 ...
- springboot 使用war包部署
ps://www.cnblogs.com/jiaoyiping/p/4251718.html
- 【leetcode 968. 1028. 从先序遍历还原二叉树】解题报告[待完善...]
思路:用一个栈来管理树的层次关系,索引代表节点的深度 方法一: TreeNode* recoverFromPreorder(string S) { /* 由题意知,最上层节点深度为0(数字前面0条横线 ...