ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RocketMQ架构:消息零丢失

RocketMQ架构:消息零丢失

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 零丢失最佳实践

  1. 生产端使用同步发送,或异步发送加本地消息表;
  2. 同步模式下设置足够重试次数;
  3. Broker 设置brokerRole=SYNC_MASTER;
  4. Broker 设置flushDiskType=SYNC_FLUSH;
  5. 消费端业务处理成功后再返回CONSUME_SUCCESS;
  6. 异步消费场景配合本地消息表加定时扫描;
  7. 所有重发和补偿路径都做幂等。

六、总结

RocketMQ 零丢失要贯穿三段链路:

Producer:同步发送 + 重试 Broker:同步刷盘 + 主从同步 Consumer:处理成功再确认 业务兜底:本地消息表 + 定时扫描

可靠性不是免费午餐。同步刷盘和同步复制都会降低吞吐,实际项目中要根据业务到底是 AP 优先还是 CP 优先来做取舍。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进