Java面试要点:MQ消息积压、重复消费、死信队列与重试风暴
前言
MQ 让系统解耦,但也会带来消息积压、重复消费、顺序错乱、死信堆积和重试风暴。面试时要讲清楚:积压怎么看,为什么积压,怎么快速恢复,怎么保证业务不重复执行。
典型现象
- 消费 lag 持续增长。
- 订单状态延迟更新。
- 短信、站内信、积分发放延迟。
- 死信队列消息变多。
- 消费者日志大量重复异常。
- 下游服务被重试流量打爆。
排查流程
flowchart TD
A[消息积压] --> B[确认Topic/Queue]
B --> C[看生产速率和消费速率]
C --> D{消费是否报错}
D -->|是| E[查异常和死信]
D -->|否| F[查消费耗时和并发]
E --> G[修复数据或代码]
F --> H[扩容消费者/提高并发]
G --> I[补偿和重放]
H --> I
消息积压原因
消费者异常
消息反复消费失败,进入重试或死信。
常见原因:
- 参数格式不兼容。
- 数据库约束冲突。
- 下游接口异常。
- 代码空指针。
- 幂等逻辑错误。
处理方式:
- 先看异常日志和消息体。
- 区分脏数据和系统性故障。
- 脏数据进入死信或人工处理。
- 系统性故障先修代码或回滚。
消费速度不足
消费者没有报错,但处理太慢。
常见原因:
- 单条消息处理逻辑太重。
- 消费者线程数不足。
- 分区或队列数不足。
- 下游数据库慢。
- 消费中串行调用第三方接口。
处理方式:
- 增加消费者实例。
- 增加消费线程。
- 扩分区或队列。
- 批量处理。
- 将慢步骤继续异步拆分。
重试风暴
下游服务异常时,消费者不断重试,导致下游更慢。
处理方式:
- 设置最大重试次数。
- 指数退避。
- 熔断降级。
- 失败消息进入延迟队列或死信。
- 修复后再补偿。
重复消费
MQ 通常只能保证至少一次投递,重复消费是必须面对的问题。
幂等方案:
- 业务唯一键。
- 消费记录表。
- Redis setnx。
- 数据库唯一索引。
- 状态机前置校验。
例如订单支付成功消息,消费前先判断订单状态。如果已经是已支付,就直接返回成功,不再重复更新资金流水。
死信队列
死信不是垃圾桶,而是待处理异常消息池。
死信治理要包含:
- 死信数量监控。
- 死信消息查询。
- 失败原因记录。
- 人工修复入口。
- 重放工具。
- 重放幂等保护。
没有死信处理机制的系统,消息失败后只能靠查日志和手工改库,生产风险很高。
Kafka、RocketMQ、RabbitMQ 差异
| MQ | 积压关注点 | 处理思路 |
|---|---|---|
| Kafka | consumer lag、partition 数 | 增加消费者到分区上限、优化批量消费 |
| RocketMQ | consumer offset、重试队列 | 调整消费线程、处理重试和死信 |
| RabbitMQ | ready/unacked 数 | 看消费者 ack、prefetch、死信交换机 |
生产止血
- 暂停非核心生产者。
- 扩容消费者。
- 临时关闭异常消费逻辑。
- 将失败消息转入死信。
- 对下游服务限流保护。
- 修复后按时间段补偿。
积压恢复时不要无限扩容。消费者扩容会增加数据库和下游压力,可能把故障从 MQ 转移到 DB。
高频面试题
MQ 积压怎么排查?
先确认哪个 Topic 或 Queue 积压,再看生产速率、消费速率、消费者异常、消费耗时、分区队列数和下游依赖。根据原因决定修代码、扩容还是补偿。
重复消费怎么解决?
通过业务唯一键、消费记录表、唯一索引、状态机校验等方式保证幂等。消费者要允许消息重复到达,但业务结果不能重复。
死信消息怎么处理?
死信要监控、查询、记录失败原因,支持人工修复和安全重放。重放前必须保证幂等,避免重复扣款、重复发券等问题。
总结
MQ 故障排查的核心是生产速率、消费速率、失败原因和幂等补偿。面试时要体现“异步系统不是只发消息,还要能查、能补、能重放”。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Junly博客!
评论


