RocketMQ 消息堆积了怎么处理?

#中间件#面试八股

RocketMQ 消息堆积了怎么处理?

结论:堆积(Backlog)只是"生产速度 > 消费速度"的结果,处理顺序应该是先定位原因、再扩容、最后才考虑跳过消息。绝大多数堆积不是消费者不够多,而是消费逻辑变慢、消费线程被阻塞、或者队列数太少导致扩容无效。

一、先定位,别急着加机器

发现堆积
├─ 消费者进程是否存活? ── 否 → 重启/查 OOM、GC、线程池打满
├─ 消费 TPS 是否下降? ── 是 → 查下游依赖(DB 慢 SQL、第三方接口超时)
├─ 队列数是否远小于消费者数? ── 是 → 加机器无效,需扩队列
├─ 是否顺序消费? ── 是 → 单队列串行,加线程无效,只能扩队列或拆 Topic
└─ 是否生产突增? ── 是 → 先扩容消费,再评估是否限流生产
排查命令作用
mqadmin consumerProgress -g 组名 -n namesrv查看每个队列的堆积量与位点差
mqadmin consumerStatus -g 组名 -n namesrv查看消费者连接、订阅、消费 RT
mqadmin topicStatus -t 主题 -n namesrv查看队列最大位点、最小位点
mqadmin brokerStatus -b 主节点 -n namesrv查看磁盘、页缓存、写入 TPS

consumerProgress 输出里的 diff 就是堆积量,diff 持续增长说明消费确实跟不上,而不是"监控延迟"。

二、三类堆积与对应手段

类型特征处理手段
消费者故障diff 突然飙升,消费者掉线恢复进程、排查 GC/线程池,堆积会自动消化
消费变慢diff 缓慢上涨,消费 RT 变高优化单条逻辑(批量、异步、去掉同步 RPC)
能力不足队列打满,消费线程持续忙扩队列 + 扩消费者;顺序消费只能扩队列

扩消费者有一条硬约束:同一消费组内,一个队列只会分配给一个消费者。4 个队列配 8 个消费者,后 4 个是空闲的,白白浪费。

三、提升消费能力的配置

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_consumer");
consumer.setNamesrvAddr("10.0.0.11:9876");
consumer.subscribe("ORDER_TOPIC", "*");
// 并发消费:线程数按 CPU 与下游承受能力调,不是越大越好
consumer.setConsumeThreadMin(64);
consumer.setConsumeThreadMax(64);
// 一次拉多少条、批量交给监听器
consumer.setPullBatchSize(64);
consumer.setConsumeMessageBatchMaxSize(32);
// 单队列缓存阈值,防止拉太快把内存撑爆
consumer.setPullThresholdForQueue(2000);
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, ctx) -> {
    handleBatch(msgs);          // 批量写库,比逐条快一个数量级
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();

顺序消费(MessageListenerOrderly)不能靠加线程提升,因为同一队列由同一个线程加锁串行处理;此时只能增加队列数或把大 Topic 按业务拆开。

四、扩大队列的正确姿势

# 增加读写队列数(4.x 支持在线调整,但只对新消息生效)
sh bin/mqadmin updateTopic -n 10.0.0.11:9876 -c OrderCluster \
   -t ORDER_TOPIC -r 16 -w 16

注意三点:增加队列会触发消费者重新分配队列,短暂重复消费;历史消息仍留在旧队列,堆积消化速度不变;扩队列后消息分布可能不均,需要观察新增队列的写入量。

五、常见追问

  • 积压几百万条,能不能直接重置位点跳过? 能但要评估:resetOffsetByTime 跳过的消息就是永久丢失,一般只用于"消息已过期、业务已对账"的场景,且必须留痕。
  • 堆积会把磁盘写满吗? 会。磁盘使用率超过 diskMaxUsedSpaceRatio(默认 75%)后 Broker 拒绝写入,影响所有 Topic,所以堆积必须配告警。
  • 堆积消息会被自动删掉吗? 会。超过 fileReservedTime(默认 72 小时)且到达删除时间点(deleteWhen,默认凌晨 4 点)的 CommitLog 会被清理,未消费的消息随之消失。
  • 消费失败一直重试会怎样? 并发消费默认重试 16 次,之后进入 %DLQ%消费组名 死信队列,需要人工处理并告警。

小结

  • 先看 consumerProgress 的 diff 走势,区分故障、变慢、能力不足三种堆积。
  • 扩消费者受队列数限制,顺序消费加线程无效,正确做法是扩队列或拆 Topic。
  • 消费侧优化优先级高于扩容:批量处理、异步化、去掉同步远程调用。
  • 跳过消息是最后手段,会丢数据;堆积必须配磁盘水位告警,否则会演变成全局故障。
笔记加载中…