前言

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 故障排查的核心是生产速率、消费速率、失败原因和幂等补偿。面试时要体现“异步系统不是只发消息,还要能查、能补、能重放”。