RabbitMQ消息可靠性
RabbitMQ消息可靠性
生产者可靠性
生产者重连
- Spring Boot 的 RabbitMQ 客户端(基于
RabbitTemplate)默认已经实现了连接恢复和重连机制。 - 自动连接恢复
- Spring AMQP 使用了
CachingConnectionFactory来管理 RabbitMQ 连接。CachingConnectionFactory内置了连接恢复机制,当连接断开时,它会自动尝试重新连接。- 重连次数:默认情况下,
CachingConnectionFactory会无限次重试。- 如果断开RabbitMQ容器会无限同步报错
- 重连间隔:默认重连间隔为 5 秒。
- 重连次数:默认情况下,
- Spring AMQP 使用了
- 配置连接重连机制
- 如果希望限制连接 RabbitMQ 失败后的重连次数,需要通过编程方式配置
CachingConnectionFactory,因为 Spring Boot 没有直接提供相关的配置属性。
- 如果希望限制连接 RabbitMQ 失败后的重连次数,需要通过编程方式配置
生产者确认
- 在默认情况下,生产者发送消息到 RabbitMQ 后,无法确定消息是否成功到达 RabbitMQ 服务器。如果 RabbitMQ 服务器崩溃或网络故障,可能会导致消息丢失。生产者确认机制通过以下方式解决这个问题:
- 确认消息到达:RabbitMQ 服务器在接收到消息后,会向生产者发送一个确认(ack)。
- 处理消息丢失:如果消息无法路由到队列(如交换机不存在或队列未绑定),RabbitMQ 会向生产者发送一个否定确认(nack)。
- 确认消息到达的流程
- 生产者发送消息:
- 生产者将消息发送到 RabbitMQ 服务器。
- 消息会被发送到指定的交换机和路由键。
- RabbitMQ 处理消息:
- RabbitMQ 服务器接收到消息后,会尝试将消息路由到绑定的队列。
- 如果消息成功路由到队列,RabbitMQ 会将该消息持久化(如果队列和消息都配置了持久化)。
- 发送确认(ack):
- 如果消息成功到达 RabbitMQ 并被处理,RabbitMQ 会向生产者发送一个确认(ack)。
- 确认中通常包含一个唯一的标识符(如
correlationId),用于标识是哪条消息被确认。
- 生产者处理确认:
- 生产者接收到确认后,可以确定消息已经成功到达 RabbitMQ。
- 生产者可以根据确认结果执行后续逻辑(如记录日志、更新状态等)。
- 生产者发送消息:
MQ可靠性
-
通过配置保证交换机、队列以及发送的所有消息进行持久化到磁盘,保证消息重启后不会丢失
-
@Bean public Queue directQueue01() { return new Queue("directQueue01", true); } -
通过
new Queue("directQueue01", true)创建的队列是持久化的(true表示持久化)。这意味着即使RabbitMQ服务器重启,队列本身仍然存在。 -
然而,队列的持久化并不保证队列中的消息也是持久化的。消息的持久化取决于消息发布时的设置。要确保消息持久化,需要在发布消息时设置消息的
deliveryMode为2(持久化模式)。
-
-
使用惰性队列
- 消息存储在磁盘:
- 惰性队列会将消息直接写入磁盘,而不是优先存储在内存中。
- 普通队列会将消息存储在内存中,只有在内存不足时才会将消息写入磁盘。
- 消息积压严重:当生产者发送消息的速度远大于消费者处理消息的速度时,惰性队列可以避免内存被大量占用。
- 消息存储在磁盘:
-
创建惰性队列
-
@Configuration public class RabbitMQConfig { @Bean public Queue lazyQueue() { return QueueBuilder.durable("lazyQueue") .lazy() // 设置为惰性队列 .build(); } } -
或者设置队列参数:
x-queue-mode,值为lazy。 -
@Service public class LazyQueueListener { @RabbitListener( bindings = @QueueBinding( value = @Queue( name = "lazyQueue", // 队列名称 durable = "true", // 队列持久化 arguments = @Argument(name = "x-queue-mode", value = "lazy") // 设置为惰性队列 ), exchange = @Exchange(name = "directExchange", type = "direct"), // 绑定到交换机 key = "lazy.routing.key" // 路由键 ) ) public void listenLazyQueue(String message) { System.out.println("Received message from lazyQueue: " + message); } }
-
-
注意事项
-
只有队列和消息都设置为持久化,才能确保消息不会丢失。惰性队列不会改变消息的持久化属性,对于非持久化消息,即使它们被写入磁盘,RabbitMQ 也不会尝试恢复它们
-
public void sendNonPersistentMessage(String message) { MessageProperties properties = new MessageProperties(); properties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); // 设置消息为非持久化 Message msg = new Message(message.getBytes(), properties); rabbitTemplate.send("exchange", "routingKey", msg); System.out.println("Sent non-persistent message: " + message); }
-
-
如果队列是非持久化的,即使消息是持久化的,队列在重启后也会丢失,消息自然也无法恢复。
-
持久化只能确保在 RabbitMQ 正常关闭或重启时消息不丢失。
-
-
如果队列是持久化的,那么发送到该队列中的消息默认就是持久化的
-
下表为MQ路由到死信队列的机制
| 情况 | 结果 |
|---|---|
| 没有消费者消费消息 | 消息一直存储在队列中,直到被消费或过期。 |
| 消息过期(TTL) | 消息被移除,如果配置了死信队列,会被路由到死信队列。 |
| 队列达到最大长度 | 最早的消息被移除,如果配置了死信队列,会被路由到死信队列。 |
| 队列被删除 | 队列中的所有消息被清除。 |
| RabbitMQ 服务器重启 | 持久化的队列和消息会保留,非持久化的队列和消息会丢失。 |
| 配置了死信队列(DLX) | 消息被拒绝、过期或达到队列最大长度时,会被路由到死信队列。 |
消费者可靠性
消息的回执
- ack(Acknowledge)
- 含义:
ack表示消息已被成功处理。 - 行为:
- RabbitMQ会将该消息从队列中移除。
- 消息不会被重新投递。
- 含义:
- nack(Negative Acknowledge)
- 含义:
nack表示消息处理失败,但可能需要重试。 - 行为:
- RabbitMQ会根据配置决定是否重新将消息放回队列(重新投递)。
- 可以一次性
nack多条消息(批量处理时)。
- 含义:
- reject
- 含义:
reject表示消息处理失败,且无需重试。 - 行为:
- RabbitMQ会直接丢弃消息或将其转移到死信队列(如果配置了死信队列)。
- 只能拒绝单条消息(不能批量拒绝)。
- 含义:
消息确认模式
-
消息确认模式决定了消费者如何处理消息的确认行为,分为以下三种:
-
默认模式:
AUTO。-
none:不处理
-
行为:消息一旦被消费者接收,Spring AMQP会自动发送
ack给消息代理,消息代理会立即将消息标记为已处理并从队列中移除。 -
在此模式下,消息发送后不会等待任何确认。无论消费者是否成功处理消息,RabbitMQ都会认为消息已被处理。
-
特点:
- 实现简单,适合对消息丢失不敏感的场景。
- 如果消费者在处理消息时发生异常,消息可能会丢失,因为消息已经被确认。
-
-
Manual模式
- 描述:在此模式下,开发者需手动确认消息处理结果。需要显式发送
ack、nack或reject。
- 描述:在此模式下,开发者需手动确认消息处理结果。需要显式发送
-
Auto模式
- 描述:在此模式下,Spring AMQP会自动确认消息。如果消息处理过程中未抛出异常,则自动发送
ack;若抛出异常,则根据配置发送nack或reject。 - 如果是业务异常,则返回
nack - 如果是消息处理或者校验异常,则返回
reject
- 描述:在此模式下,Spring AMQP会自动确认消息。如果消息处理过程中未抛出异常,则自动发送
-
-
配置消息确认模式
-
消息确认模式通过
spring.rabbitmq.listener.simple.acknowledge-mode属性来设置。 -
spring: rabbitmq: listener: simple: acknowledge-mode: auto # 可选值:none、manual、auto
-
-
失败重试机制
-
AUTO模式下,如果消费者返回nack或抛出异常,默认情况下消息会无限重发(即RabbitMQ会不断将消息重新放回队列并投递给消费者)。为了避免消息无限重发,可以通过配置重试机制来限制消息的重发次数。 -
spring: rabbitmq: listener: simple: retry: enabled: true # 开启重试机制 max-attempts: 3 # 最大重试次数(包括第一次) initial-interval: 1000 # 初始重试间隔(毫秒) multiplier: 2.0 # 重试间隔倍数(每次重试间隔 = 上次间隔 * multiplier) max-interval: 10000 # 最大重试间隔(毫秒) -
默认行为:
- 如果重试次数用尽,Spring AMQP会记录一条错误日志,并丢弃(reject)消息。
- 如果队列配置了 死信交换器(Dead-Letter Exchange, DLX),消息会被转发到死信队列(Dead-Letter Queue, DLQ)。
- 如果未配置 DLX,消息会被 直接丢弃(可能造成数据丢失,需谨慎)。
-
-
自定义错误处理策略
-
可以通过实现
MessageRecoverer接口定义重试失败后的行为。 -
首先配置死信交换机和死信队列并进行绑定,然后注入
MessageRecoverer -
@Bean public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) { return new RepublishMessageRecoverer(rabbitTemplate, "error-exchange", "error-routing-key"); } -
通过实现
MessageRecoverer接口定义的重试失败处理逻辑,默认情况下会全局生效(即所有队列的消费者失败后都会走该逻辑)。 -
与死信队列(DLQ)的配合:
如果队列已配置 DLQ,重试失败的消息会优先进入 DLQ,而不会触发
MessageRecoverer。需根据业务需求选择方案。
-
消费者网络中断的影响
-
消费者连接断开:如果消费者与RabbitMQ之间的网络中断(如断网、宕机),RabbitMQ会检测到连接丢失。
-
自动重新入队:连接断开后,所有未确认的消息(包括未收到ACK/NACK的消息)会被自动重新放回队列,等待其他消费者处理。这是RabbitMQ的默认行为,确保消息不丢失。
-
消息是否会一直等待?
- 短期等待:在网络中断期间,消息会短暂处于“未确认”状态,但一旦RabbitMQ检测到消费者断开连接,就会立即将消息重新入队。
- 不会无限阻塞:RabbitMQ不会永久等待消费者响应。通过心跳检测(Heartbeat)或TCP超时机制,MQ能快速发现断连,触发消息重新入队。
-
心跳机制(Heartbeat)
-
启用心跳(默认开启)可更快检测断连。例如,设置心跳间隔为60秒:
-
spring: rabbitmq: connection-timeout: 1000 requested-heartbeat: 60
-
消费者处理超时的场景
- 如果消费者获取了消息但未在 TTL 时间内处理完毕:
- 消息状态:消息处于“未确认”状态(Unacknowledged)。
- TTL 触发:即使消息正在被处理,只要存活时间超过 TTL,RabbitMQ 会强制将其标记为过期。
- 结果:
- 消息会被删除或转发到死信队列(如果配置了 DLX)。
- 消费者后续的确认操作(ACK/NACK)将失效,因为消息已不存在。
业务幂等性
-
幂等性(Idempotence) 是指同一操作多次执行所产生的影响与一次执行的影响相同。无论操作执行一次还是多次,系统的状态都保持一致。简单来说,就是“重复调用不改变结果”。
-
为什么需要幂等性?
- 在分布式系统、网络通信或高并发场景中,以下情况可能导致重复操作:
- 网络重传:客户端未收到响应,自动重试请求。
- 消息队列重复投递:消息中间件可能因ACK超时重新发送消息。
- 用户重复提交:用户短时间内多次点击提交按钮。
- 若不保证幂等性,可能导致:
- 重复扣款(支付系统)
- 重复下单(电商系统)
- 数据不一致(数据库操作)
- 在分布式系统、网络通信或高并发场景中,以下情况可能导致重复操作:
-
幂等性的实现方案
-
唯一标识符(ID)
- 核心思想:为每个操作分配唯一标识(如订单号、请求ID),通过标识符去重。
- 数据库唯一索引:利用数据库的唯一约束拦截重复数据插入。
- 使用订单号作为唯一标识,支付前检查订单状态。
- 调用第三方支付接口时,传递唯一
out_trade_no,确保同一订单只扣款一次。
- 核心思想:为每个操作分配唯一标识(如订单号、请求ID),通过标识符去重。
-
通过业务逻辑实现幂等性
- 数据库状态字段:在更新时校验当前状态。
-
Token 机制(一次性令牌)
- 核心思想:服务端生成一次性 Token,客户端提交时携带,校验后失效。
-
-
全局唯一ID的生成
- 方案:
- UUID:简单但无序,可能影响数据库性能。
- 雪花算法(Snowflake):生成有序递增的ID(如Twitter的分布式ID生成方案)。
- 数据库自增序列:需分库分表时使用步长隔离。
- 方案:

浙公网安备 33010602011771号