RocketMQ 消息零丢失,怎么实现?
RocketMQ 的消息从生产到消费同样经历三个阶段:生产、存储、消费。零丢失不能只靠某个参数,而要在每个阶段分别设置可靠策略。
一、生产阶段怎么保证
三种发送方式
RocketMQ Producer 提供三种发送 API:
// 同步发送publicSendResultsend(Messagemsg)throwsMQClientException,RemotingException,MQBrokerException,InterruptedException{}// 异步发送publicvoidsend(Messagemsg,SendCallbacksendCallback)throwsMQClientException,RemotingException,InterruptedException{}// 单向发送publicvoidsendOneway(Messagemsg)throwsMQClientException,RemotingException,InterruptedException{}单向发送不关心结果,最不可靠。要保证生产阶段不丢消息,优先使用同步发送。
同步发送返回的状态包括:
SEND_OK:消息发送成功;FLUSH_DISK_TIMEOUT:Broker 刷盘超时;FLUSH_SLAVE_TIMEOUT:主从同步超时;SLAVE_NOT_AVAILABLE:从节点不存在。
严格业务可以只把SEND_OK视为成功,其他状态交给本地消息表兜底。
失败重试
同步发送默认重试 3 次,可以手动调大:
producer.setRetryTimesWhenSendFailed(10);核心逻辑里,同步发送失败会继续进入下一轮重试;异步和单向发送则不重试。
二、Broker 端怎么保证
第一板斧:严格副本同步
默认 Broker 是ASYNC_MASTER,Master 写入后不等待 Slave 同步。要保证数据冗余,可以改成:
brokerRole=SYNC_MASTER这样 Master 会等 Slave 同步完成后再通知 Producer。
第二板斧:同步刷盘
RocketMQ 默认是异步刷盘。Broker 先把消息写入 Page Cache 就返回,后台再刷盘。如果机器在刷盘前宕机,消息会丢失。
要改成同步刷盘:
flushDiskType = SYNC_FLUSH对应枚举:
publicenumFlushDiskType{SYNC_FLUSH,ASYNC_FLUSH}异步刷盘默认约 10 秒执行一次。同步刷盘可靠性更高,但吞吐会明显下降。
Broker 零丢失配置
# master flushDiskType = SYNC_FLUSH brokerRole=SYNC_MASTER # slave brokerRole=slave flushDiskType = SYNC_FLUSH这条配置表示:Master 先落盘,再同步给 Slave,Slave 同步并落盘后才返回成功。
三、消费端怎么保证
消费端要保证业务真正处理成功后再确认消费。RocketMQ 在并发消费模式下,监听器返回CONSUME_SUCCESS才表示消费完成:
consumer.registerMessageListener(newMessageListenerConcurrently(){@OverridepublicConsumeConcurrentlyStatusconsumeMessage(List<MessageExt>msgs,ConsumeConcurrentlyContextcontext){for(MessageExtmsg:msgs){Stringstr=newString(msg.getBody());// 同步执行真正的业务逻辑process(str);}// 全部处理成功后,再确认消费returnConsumeConcurrentlyStatus.CONSUME_SUCCESS;}});如果业务处理抛异常,不要返回成功,让消息进入重试。消费失败默认会重试 16 次,最终进入死信队列。
异步消费怎么办
如果监听器内部把消息交给独立业务线程池异步处理,不能立即返回CONSUME_SUCCESS。否则线程池还没处理完,消息已经被确认,进程挂掉就会丢消息。
这种场景需要业务层兜底:处理成功后再更新消息状态,或者使用本地消息表加定时扫描。
四、终极兜底:本地消息表加定时扫描
前面几板斧仍然不能覆盖所有异常。例如 Broker 返回成功但集群集体断电,或者消费回调执行前 JVM 假死。
极端严格场景可以采用本地消息表:
发送前:消息写入本地表,状态=待发送 发送:同步或异步发送 MQ 消费成功后:通过 RPC 更新消息状态 定时任务:扫描过期未确认消息,重新发送这套方案的特点:
- 可靠性高,但吞吐低;
- 必须做好幂等;
- 可以缩小本地表规模,只跟踪发送失败或消费失败的消息。
五、RocketMQ 零丢失最佳实践
- 生产端使用同步发送,或异步发送加本地消息表;
- 同步模式下设置足够重试次数;
- Broker 设置
brokerRole=SYNC_MASTER; - Broker 设置
flushDiskType=SYNC_FLUSH; - 消费端业务处理成功后再返回
CONSUME_SUCCESS; - 异步消费场景配合本地消息表加定时扫描;
- 所有重发和补偿路径都做幂等。
六、总结
RocketMQ 零丢失要贯穿三段链路:
Producer:同步发送 + 重试 Broker:同步刷盘 + 主从同步 Consumer:处理成功再确认 业务兜底:本地消息表 + 定时扫描可靠性不是免费午餐。同步刷盘和同步复制都会降低吞吐,实际项目中要根据业务到底是 AP 优先还是 CP 优先来做取舍。