消息整体处理过程,这里我们将消息的整体处理阶段分为3个阶段进行分析:
1、消息发送阶段:Producer 将消息发送到 Broker。
2、消息存储阶段:Broker 接收并持久化消息。
3、消息消费阶段:Consumer 从 Broker 拉取消息并处理。
一、Producer发送消息阶段
1、同步发送
机制:Producer 发送消息后,会同步等待 Broker 返回发送结果(SendResult)。只有收到明确的成功ACK,才认为发送成功。
可靠性:这是最高效的发送方式,能保证消息肯定送达,如果发送失败(如网络超时、Broker 异常),可以立即捕获异常并进行重试。
2、异步发送
机制:Producer 发送消息后,不等待响应,而是设置一个回调函数。当 Broker 返回响应时,会触发这个回调函数来处理结果。
可靠性:性能比同步发送更高,但可靠性同样有保障。如果发送失败,可以在回调函数中进行重试。
适用场景:对性能要求极高,且能容忍少量消息丢失(通过重试可以弥补)的场景。
3、单向发送(不推荐)
机制:Producer 只负责发送消息,不等待响应,也不设置回调。即“发后即忘”。
可靠性:性能最高,但不保证消息可靠投递。可能因为网络抖动等原因导致消息丢失。通常用于日志收集等对可靠性要求不高的场景。
4、重试机制
RocketMQ 的 Producer 自带重试逻辑。当发送失败时(同步/异步,单向不含重试),它会自动重试,默认重试 2 次。
你可以通过 setRetryTimesWhenSendFailed参数来设置重试次数。
5、事务消息(最高可靠性保证)
解决的问题:确保本地事务执行和消息发送这两个操作的原子性。例如,在支付成功后,需要同时更新订单状态(本地数据库操作)和发送一条支付成功的消息。
两阶段提交流程:
第一阶段:发送半消息(Half Message):Producer 向 Broker 发送一条“对 Consumer 不可见”的消息。
第二阶段:执行本地事务:Producer 执行本地数据库事务(如更新订单状态)。
第三阶段:提交或回滚:
如果本地事务成功,Producer 向 Broker 发送 Commit 指令,此时半消息才真正对 Consumer 可见。
如果本地事务失败,Producer 发送 Rollback 指令,Broker 会丢弃这条半消息。
状态回查(Transaction Check):如果 Producer 在执行完本地事务后,由于宕机等原因没有向 Broker 发送 Commit/Rollback 指令,Broker 会定期向 Producer 回查该消息的最终状态(Commit or Rollback ?),从而决定消息的最终去向。
二、Broker处理消息阶段
这个阶段要解决的核心问题是:如何确保消息在 Broker 上不丢失?持久化 + 高可用(主从复制)
持久化机制
Commit Log:所有主题(Topic)的消息都顺序写入同一个文件(Commit Log)。顺序写磁盘的速度非常快,甚至可以媲美内存读写。
刷盘策略:
同步刷盘(SYNC_FLUSH):Broker 收到消息后,必须将其持久化到磁盘后才返回成功ACK给Producer。这是最可靠的模式,但性能较低。
异步刷盘(ASYNC_FLUSH):Broker 收到消息后,先写入内存的 Page Cache,然后就返回成功ACK。再由一个后台线程定期将内存中的数据刷到磁盘。性能很高,但如果 Broker 突然宕机,内存中未刷盘的消息会丢失。
配置建议:对可靠性要求极高的场景(如金融交易),采用同步刷盘;对性能要求更高、可容忍少量丢失的场景,采用异步刷盘。
高可用机制(主从复制)
解决单点故障:如果只有一个 Broker,一旦它宕机,整个消息服务就不可用了。
复制模式:
同步复制(SYNC_MASTER):主节点(Master)将消息成功持久化后,还会等待至少一个从节点(Slave)也成功持久化,才返回成功ACK给Producer。这样即使主节点宕机,从节点上也有一模一样的消息,可以切换成新的主节点继续服务。这是最可靠的模式。
异步复制(ASYNC_MASTER):主节点持久化消息后立即返回ACK,然后异步地将数据复制给从节点。性能更好,但如果主节点在数据复制完成前宕机,可能会丢失部分消息。
最佳实践组合:要保证 Broker 端消息绝对不丢失,推荐 同步刷盘 + 同步复制。当然,这会以性能为代价。
三、消息消费的可靠性(Broker -> Consumer)
如何确保消息一定能被 Consumer 成功处理?核心机制:消费端ACK + 重试机制(重试队列/死信队列)
消费模式
集群消费(CLUSTERING):同一条消息只能被同一个消费者组中的一个消费者消费。这是默认模式,用于负载均衡。
广播消费(BROADCASTING):同一条消息会被同一个消费者组中的每一个消费者消费。
ACK(确认)机制
1、Consumer 拉取到消息并处理完成后,必须向 Broker 返回一个消费成功(ACK)的响应。
2、默认情况:Consumer 是监听模式,Broker 会主动将消息推送给 Consumer(底层是 Consumer 长轮询),Consumer 处理完后返回 ACK。
重试机制
如果 Consumer 消费失败(如业务逻辑处理异常、网络异常),它会返回 RECONSUME_LATER。
Broker 收到这个信号后,会将这条消息放入一个特殊的 重试队列。
消息会在延迟一段时间后(重试次数越多,延迟时间越长)被重新投递给 Consumer。默认最多重试 16 次。
死信队列(Dead-Letter Queue)
如果一条消息重试了 16 次后仍然失败,它就不会再被继续投递,而是被转移到另一个特殊的队列,称为 死信队列(%DLQ%+ConsumerGroupName)。
消息进入死信队列后,需要人工介入处理。可以查看日志,分析消费失败的具体原因,修复代码后,再将死信消息重新发送回正常的 Topic 进行消费。
消费幂等性
问题:由于重试机制的存在,同一条消息可能会被Consumer收到多次(例如,Consumer 处理完业务后,在返回 ACK 前宕机了,Broker 会认为消费失败并重新投递)。
解决方案:这需要消费端的业务逻辑自己保证幂等性。即无论同一条消息被消费多少次,最终的业务结果都应该是一致的。
常见方法:
使用数据库唯一键(如订单ID)防止重复插入。
使用乐观锁(如版本号)。
使用分布式锁或查询状态机,判断消息是否已被处理过。
四、为什么会出现重复消费的原理
基本概念
RocketMQ是以【consumer group+queue】来确认消息消费进度
消费进度(Offset)的管理粒度是 Consumer Group + Message Queue。同一个 Consumer Group 下,每个 Message Queue 都有一个独立的消费进度。
通过【group+offset】来标记【queue】消费进度
Broker 端会存储一个映射关系:<Consumer Group, Topic, QueueId> -> Offset,这个 Offset 值表示该消费者组对这个队列即将消费的下一条消息的位置。
消费成功之后都会返回一个ack消息告之broker更新offset
Consumer 在处理完消息后,会向 Broker 返回消费结果(ACK),但是RocketMQ并不是按一条一条消息来做ack,而是根据一次拉取批量来做消息ack
RocketMQ 的 ACK 机制比这更精细。它确实是按批次进行ACK操作,但确认的进度是批内最后一条消息的Offset,这意味着批内的消息是逐条处理,但进度是整体提交。
例子
RocketMQ Consumer 的 ACK 机制是 “顺序的、非精准的批量进度提交”。
1、顺序处理:Consumer 从 Broker 拉取一批消息(比如 32 条)到本地。然后,严格按照顺序一条一条地提交给业务监听器处理。
2、进度提交:当这一批消息中的第 N 条消息被成功消费后,Consumer 并不会立即向 Broker 报告第 N 条的进度。它会等待一个时机(比如定时任务,或者处理完一定数量的消息后),将当前已成功处理的消息的最大 Offset 提交给 Broker。
例如,拉取了 Offset 为 100~131 的 32 条消息。
如果消息 100, 101, 102, 103 都处理成功了,Consumer 可能会先将 Offset=104 提交给 Broker。这意味着 100~103 的消息都被确认消费了。
如果消息 104 处理失败,它会被挂起(suspend),然后 Consumer 会继续尝试处理 105, 106…
失败处理与重复消费的根源:
如果消息 104 处理失败,Consumer 会返回 RECONSUME_LATER。
此时,已经提交的进度 Offset=104 并不会回滚。
由于消息 104 失败,Consumer 会暂停当前队列的消费,并将从 104 开始的所有剩余消息(105~131) 一起返回给 Broker,表示这批消息处理失败。
Broker 会将这些失败的消息(104~131)放入重试队列。
问题就在这里:Offset=104 已经提交给 Broker,意味着 Broker 认为消息 100~103 已经成功消费。但当 104~131 这批消息被重新投递时,消息 104 会再次被消费,而 105~131 这些本已成功处理的消息也会被再次消费,这就导致了批量消息的重复消费。
重复消费的根本原因总结
导致重复消费的根本原因可以归结为两点:
进度提交机制:ACK 是进度提交,而不是消息确认。它提交的是“这个Offset之前的消息都成功了”,而不是“某条消息成功了”。当某条消息失败时,它所在批次中已经处理成功的后续消息也会被重新投递。
网络或客户端异常:即使在最理想的情况下,如果 Consumer 在处理完一批消息后、提交进度给 Broker 之前发生宕机或网络断开,那么当 Consumer 重启或重连后,它会从上次提交的进度点开始消费,这也会导致上次已经处理过但未提交进度的消息被再次消费。
安全性保障:跳转
重复消费:跳转
本文详细分析了RocketMQ消息处理的三个阶段:Producer发送、Broker处理和Consumer消费,探讨了其安全机制、重试策略以及为避免重复消费所采取的措施,包括同步/异步刷盘、主从模式和消费超时时间设置。
1656

被折叠的 条评论
为什么被折叠?



