一、 生产者可靠性 (Producer Reliability) 主要防止消息在发送到MQ的过程中丢失:
生产者重连机制:由于网络波动导致连接MQ失败时,可以配置超时时间和重试次数。由于这种重试是阻塞式的(会卡住主线程),对性能影响较大,因此建议将超时时间和等待间隔设置得短一些(如200毫秒)。
生产者确认机制 (Publisher Confirm):消息成功发到MQ后,MQ会异步给生产者发送回执。
如果消息成功到达队列(持久化消息则需存入磁盘后),MQ返回
ACK。如果因为MQ内部故障导致丢失,返回
NACK,此时生产者可以进行消息重发。如果路由失败(如RoutingKey写错),返回
ACK但会附带ReturnCallback异常提示。注意:确认机制有额外的网络和系统开销,除非对消息绝对可靠性要求极高,否则大多数业务不建议开启。
二、 MQ服务端可靠性 (MQ Reliability) MQ默认将消息存在内存中,速度快,但遇到MQ宕机重启会丢失数据,且内存占满时会触发 "Page Out"(将内存数据转移到磁盘),导致MQ完全阻塞,无法处理新消息。
数据持久化:将交换机、队列设置为持久的(Durable),并将消息的投递模式(Delivery Mode)设置为2(持久化消息),确保数据安全写入磁盘,重启不丢失。
Lazy Queue (惰性队列):接收到消息后直接存入磁盘,消费者需要时再加载到内存。它支持数百万条消息堆积,且持久化写入速度非常平稳,不会出现 "Page Out" 阻塞问题。在 RabbitMQ 3.12 版本之后,惰性队列已成为所有队列的默认模式。
三、 消费者可靠性 (Consumer Reliability) 防止消费者在处理消息时抛出异常导致消息丢失:
消费者确认机制:推荐将 Acknowledge 模式配置为
auto(全自动)。Spring AMQP 会拦截业务代码,正常执行完自动返回ACK;若抛出业务异常返回NACK(消息重新入队);若抛出消息转换等致命异常则返回Reject(直接丢弃消息)。失败重试与兜底策略:如果直接返回
NACK,消息会无限重新投递给消费者,造成死循环并压垮系统。解决方案是开启 Spring 的本地重试机制。当本地多次重试依然耗尽后,配置RepublishMessageRecoverer兜底方案,将这条失败的消息转发到一个专门的“异常交换机”和“异常队列”中,后续交由人工介入排查。
四、 业务幂等性保障 (Business Idempotence) 在复杂的重试机制下,消息可能会被重复消费(如消费者已处理成功,但网络原因没能返回ACK)。必须保证业务无论执行一次还是多次,结果都一致(即幂等性)。
方案一:唯一消息ID。配置 Jackson 消息转换器的
createMessageIds = true给每条消息生成唯一ID。消费者处理前先去数据库查该ID是否存在,存在则视为重复处理并丢弃。缺点是业务代码侵入性高,且增加数据库读写压力。方案二:基于业务本身做判断(推荐)。结合业务逻辑,比如支付成功后修改订单状态,可以利用数据库的乐观锁机制实现。执行 SQL 时加上状态判断条件:
update order set status = '已支付' where id = ? and status = '未支付'。这样重复消费时,由于状态已变,SQL不会生效,完美保证幂等性。
这一部分可以说是整套课程最核心的实战难点。我们继续为您整理最后一部分:延迟消息,好吗?