1. 为什么生产环境的RocketMQ总是“莫名其妙”出问题
我最早接触RocketMQ是公司业务量上来之后,Kafka那边几个大Topic的消费延迟开始频繁报警,架构组决定把一部分交易类消息切到RocketMQ上。当时想得很简单:RocketMQ文档全、社区活跃、阿里内部大规模用过,应该比我们之前自己拼出来的那套MQ省心很多。结果上线第二个月就开始“学做人”,消费堆积、消息重复、顺序乱掉、主从切换后写入超时,各种问题接二连三地冒出来。
后来我慢慢意识到,RocketMQ本身作为一款消息中间件,它的稳定性和功能完备性在开源产品里确实属于第一梯队,但生产环境里的故障,绝大多数不是“中间件坏了”,而是我们使用姿势不对:参数配置不合理、客户端API理解有偏差、容量预估严重不足、事务消息的使用时机搞错、以及最容易被忽略的——对RocketMQ底层存储和复制机制缺乏足够认识。
这篇文章不打算从“什么是RocketMQ”开始念文档,而是直接从我在生产环境里一次次踩坑、查日志、改配置、做压测的真实经历出发,把最常见的故障类型、根因分析思路、排查命令和处置方法整理出来。如果你正在做RocketMQ的运维、开发或架构设计,这篇文章应该能帮你少走很多弯路。哪怕你只是面试前临时抱佛脚,这里的实际案例也比背八股文有用得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 高频故障全景图与根因分类
2.1 消息丢失:最致命却也最容易误判
先说说最吓人的消息丢失。RocketMQ给人的印象是“比Kafka更可靠”,因为默认情况下它会做同步刷盘和主从同步复制,但这不代表消息绝对不会丢。生产里真正因为Broker崩溃导致commitlog文件损坏而丢消息的,我遇到过,但非常少。更多时候所谓的“丢消息”,其实是客户端逻辑问题。
第一个典型案例是异步发送失败后没有处理回调。RocketMQ的DefaultMQProducer提供了同步发送、异步发送和单向发送三种方式。很多人图性能用异步发送,send方法传了一个SendCallback进去,回调里只写了成功分支,失败分支直接打一行log就完了,甚至有的连log都没写。等到业务反馈某条数据对不上账的时候,去Broker上查消息,发现这条消息当时根本没写进去,但日志里又找不到报错记录——因为失败回调被忽略掉了。
第二个典型是sendOneway。单向发送本来就是“发出去就不管”,它适合日志采集这类允许丢失的场景。但我在一些对账系统里见过有人用它发核心流水,理由是“我们之前压测发现单向发送吞吐最高”。压测吞吐高是真的,但丢了消息要补数的时候,再高的吞吐也救不了你。
第三个典型是producer端的超时设置。默认sendMsgTimeout是3000毫秒,大消息、频繁FullGC、网络抖动、Broker磁盘IO过高的情况下,发送超时抛异常,但消息实际上可能已经被Broker写入并返回了,也可能完全没写入。超时异常会诱导业务系统走重试,于是出现重复消息;如果业务系统把超时当成失败并直接抛错、不重试,就可能出现“看似写失败了但实际成功了”、下游一直没收到消息的半丢失状态。
我建议所有核心业务消息至少满足以下条件:使用同步发送或带可靠回调的异步发送;send失败必须走补偿,补偿可以是重试队列、本地事务表轮询,但绝对不能只打日志;客户端设置retryTimesWhenSendFailed,但不要把重试次数调到特别大,否则消息积压在producer内存里,反而拖垮发送线程。
2.2 消费堆积:从表象到底层链路的逐层定位
消费堆积可能是RocketMQ运维里遇到最多的故障类型。它本身不是“故障”,而是“症状”。堆积的根因可以分成几类:消费能力不足、消费线程卡死、消费逻辑异常、Rebalance抖动、Broker拉取性能下降。
先看消费能力不足。RocketMQ的并发度由两个因素决定:Consumer的消费线程数和消息队列数。很多人以为把consumeThreadMin调到100,消费速度就会提升10倍,其实不然。一个Consumer实例最多能分配的队列数是它订阅的所有Topic队列数的总和,如果Topic只有4个队列,哪怕你把线程数调到50,真正能同时被这个实例消费的也只有4个队列上的消息,其他线程都在空转。遇到这类堆积,先看Topic的队列数是否够多,再看Consumer实例数是否够多。增加队列数量要谨慎,因为队列数一旦定了,虽然可以在控制台手动扩展,但扩展后会触发Rebalance,过程中可能出现短暂重复消费。
再看消费线程卡死。我们遇到过Java服务里某个消费方法调用了外部HTTP接口,外部接口因为连接池被打满导致耗时30秒以上,而消费线程的默认超时时间consumeTimeout是15分钟,表面上不会触发重试,但消费线程被长期占住,新
