凌晨3点线上报警提示Kafka某个Topic积压了上百万条消息,请你讲讲从排查瓶颈到紧急扩容清理积压的完整SOP
凌晨3点处理线上严重报警,核心原则是:“先止血恢复业务,后排查根因;防雪崩重于一切”。
Kafka积压上百万条消息,意味着数据实时性已经遭到严重破坏。以下是一份标准、冷酷且高效的凌晨3点应急处理SOP(标准作业程序)。
阶段一:初步定级与止血评估(0-10分钟)
目标:确认影响范围,决定是否需要叫醒其他同事,评估是否有引发连环雪崩的风险。
- 确认报警真实性与积压趋势:
- 查看监控看板(Grafana/Prometheus):当前Lag(积压量)是100万,生产速率(Produce Rate)和消费速率(Consume Rate)分别是多少?
- 趋势判断:是逐渐累积到100万的(慢积压),还是突然激增的(突刺型)?
- 评估业务影响:
- 该Topic对应什么业务?(核心链路如交易/支付,还是非核心如日志/埋点?)
- 核心链路:立即拉群,叫醒业务研发Owner和DBA。
- 非核心链路:记录状态,尝试单人操作恢复。
- 💥 核心警告:防下游雪崩
- 在未确认下游系统(如MySQL/Redis/ES/第三方API)抗压能力前,绝不可盲目扩容消费者! 否则清理积压产生的并发洪峰会把下游直接打挂,导致更大面积的P0故障。
阶段二:快速排查瓶颈(10-25分钟)
目标:定位是生产者突发、消费者故障,还是Kafka集群问题。
通过APM工具(如SkyWalking, Pinpoint)、日志平台或Kafka命令排查以下三种可能:
1. 消费者端问题(占90%以上)
- 实例状态:去K8s/容器平台看消费者的Pod是不是在频繁重启(CrashLoopBackOff)?是否有OOM(内存溢出)?
- 消费线程卡死:消费者状态是Running,但消费速率为0。
- 排查手段:查看业务日志,看是否有调用第三方接口超时、死锁、或者慢SQL。使用 Arthas / Jstack 快速打印线程堆栈,看
KafkaMessageListenerContainer线程阻塞在哪。
- 排查手段:查看业务日志,看是否有调用第三方接口超时、死锁、或者慢SQL。使用 Arthas / Jstack 快速打印线程堆栈,看
- 频繁Rebalance(重平衡):
- 现象:消费速率时有时无。
- 原因:消费者处理消息太慢,超过了
max.poll.interval.ms(默认5分钟),被踢出消费组,导致消费组不断重平衡,所有消费暂停。
- 毒药消息(Poison Pill):某条坏数据导致反序列化失败或业务抛出未捕获异常,一直在无限重试(Retry Storm),阻塞了当前Partition的消费。
2. Kafka集群问题(占5%)
- 查看Kafka集群Broker的CPU、IO、网络负载。
- 查看是否有节点宕机导致Partition Leader切换失败。
- 数据倾斜(Hotspot):100万积压是不是全堵在某1个Partition里?如果是,说明生产者的路由Key设计有问题。
3. 生产者端问题(占5%)
- 现象:消费速率正常甚至偏高,但生产速率暴增了10倍。
- 原因:上游在跑大批量刷数据脚本、或者遭遇了DDoS攻击、或者是某个重试Bug导致发了海量废消息。
阶段三:紧急扩容与清理积压实操(25-60分钟)
目标:根据排查结果,采取对应的清理手段。
场景A:正常消费跟不上生产(需安全扩容)
前提:下游数据库/接口确认能抗住数倍并发。
- 查看当前的分区数与消费者数量关系:
- Kafka金科玉律:一个Partition只能被同一个消费组里的一个消费者实例消费。
- 扩容策略:
- 情况1:当前消费者实例数 < Partition数
- 操作:直接在K8s/容器平台增加消费者实例(Pod)数,直至
实例数 = Partition数。
- 操作:直接在K8s/容器平台增加消费者实例(Pod)数,直至
- 情况2:当前消费者实例数 = Partition数
- 操作:此时单纯加Pod无效(会闲置)。必须先扩容Topic的Partition数量。
- 命令:
kafka-topics.sh --alter --zookeeper x.x.x.x:2181 --topic my-topic --partitions <新数量> - 再扩容:增加消费者Pod数跟上新Partition数。
- (注意:如果业务对消息顺序有严格要求,由于分区增加会改变Key的哈希路由规则,强顺序性业务不可随意扩分区,需走场景B)。
- 情况1:当前消费者实例数 < Partition数
场景B:下游抗不住 / 紧急疏散降级(旁路处理)
如果扩容会把数据库打死,或者消费逻辑极其复杂导致慢。
- 写一个极简的“搬运工”消费者:
- 连夜编写/部署一个极简服务,不查DB,不调API,只做一件事:从当前积压的Topic拉取消息,直接原封不动地写到一个临时的Topic(如
topic-temp-dump)或者写入HDFS/OSS/Redis等高速存储。
- 连夜编写/部署一个极简服务,不查DB,不调API,只做一件事:从当前积压的Topic拉取消息,直接原封不动地写到一个临时的Topic(如
- 切流:
- 让这个极简服务满载运行,快速把这100万条消息搬走。业务Topic恢复空闲,实时新数据得以正常处理。
- 后续补录:
- 白天上班后,再安排任务从临时存储/Topic里慢慢回放、补偿这100万条旧数据。
场景C:毒药消息/无限重试卡死(跳过积压)
如果发现是某几条坏数据导致报错卡死,或者这100万条数据是上游发错的废数据(例如日志),可以直接抛弃。
- 修改偏移量(Reset Offsets)直接跳到最新:
- 停止当前消费者服务。
- 使用命令将消费组的偏移量重置到最新(跳过这100万条):
kafka-consumer-groups.sh --bootstrap-server broker1:9092 --group my-group --topic my-topic --reset-offsets --to-latest --execute - 重启消费者服务,直接消费新产生的实时数据。
场景D:消费者处理太慢导致的频繁Rebalance
不需要扩容,只需临时改配。
- 临时调大
max.poll.interval.ms(例如从 300000 改为 900000)。 - 临时调小
max.poll.records(例如从 500 改为 50),让单次拉取的数据量变少,保证在规定时间内处理完,避免被踢出消费组。 - 重启消费者。
阶段四:观察与复盘(修复后及次日工作)
- 凌晨持续观察(30分钟):
- 盯紧Grafana面板:Lag线是否呈明确下降趋势(如断崖式下降或稳步下滑)。
- 盯紧下游组件:数据库连接池、CPU、IO是否报警。
- 确认最新产生的消息是否能在一分钟内被处理。
- 次日白天的Post-Mortem(故障复盘):
- 代码优化:将单条插入数据库改为批量插入(Batch Insert);将同步调用改为异步(CompletableFuture)。
- 架构优化:如果经常有突发洪峰,引入真正的消息死信队列(DLQ)机制。
- 监控优化:100万积压才报警可能太晚了。报警规则不应仅看“积压数量”,而应增加“消息延迟时间(Lag Time)”报警。例如:积压数可能才1万,但消费速率极慢,第一条消息已经延迟了10分钟,这就应该报警了。
💡 凌晨3点操作纪律:
- 敲击任何
alter或reset-offsets命令行之前,粘贴到记事本复查一遍,确认Topic名称和集群地址(千万别切错生产和测试环境)。 - 操作前在群里同步:“正在执行消费组偏移量重置,预计影响 xxx,耗时 x 分钟”,留下操作轨迹。
右滑查看面试常问