凌晨两点,监控弹出「消费延迟 30 分钟」。登上机器一看,Kafka 里消息堆成山,消费者进程还在,日志也没报错。这时候最容易犯的错是直接重启消费者 ------ 重启大概率没用,因为积压只是结果,原因可能在消费端、生产端、分区数或者 broker 上。
下面五步,从「有多少」到「为什么」再到「怎么处置」,按顺序走一遍。
第一步:先确认积了多少、还在不在涨
Kafka 自己就有消费组工具,不用装额外组件。
bash
kafka-consumer-groups.sh --bootstrap-server broker1:9092 \
--describe --group order-consumer
输出关键列只有四个:
CURRENT-OFFSET:这个消费组已经提交到哪LOG-END-OFFSET:这个分区最新写到哪LAG:两者之差,就是积压条数CONSUMER-ID/HOST:当前是哪个实例在消费
最关键的判断是看两次。 隔一分钟再跑一遍:
- LAG 基本不动 → 这是「历史旧账」,多半是某次消费者下线、或某个分区从来没被消费过,不是实时故障
- LAG 一路往上涨 → 消费速度跟不上生产速度,是真正的故障
分区多的时候输出几百行,用 awk 汇总总量更直观:
bash
kafka-consumer-groups.sh --bootstrap-server broker1:9092 \
--describe --group order-consumer \
| awk 'NR>1 && $6 ~ /^[0-9]+$/ {s+=$6} END {print "TOTAL_LAG", s}'
第二步:看消费端到底有没有在拉
LAG 在涨,先分清是「拉得慢」还是「根本没拉」。
bash
# 消费组状态与成员
kafka-consumer-groups.sh --bootstrap-server broker1:9092 \
--state --group order-consumer
状态只有几种,含义很明确:
Stable:组内成员稳定,消费正常在跑PreparingRebalance:正在重新分配分区,反复出现就是 rebalance 风暴Empty:组里一个活跃消费者都没有,等于全挂了
再回头看 --describe 的 CONSUMER-ID 列。如果这一列是空的、只有 -,说明分区没有被任何实例认领,消息自然越堆越多。
常见三种「进程活着但不消费」:
- 业务处理太慢被踢出组 :单条消息处理超过了
max.poll.interval.ms(默认 5 分钟),协调者认为你死了,把分区收走重分配。日志里能看到反复的Rebalance和CommitFailedException。 - 下游拖死:消费线程卡在数据库慢查询或某个 HTTP 接口上,线程池打满,poll 循环停摆。
- GC 长暂停:Full GC 十几秒,心跳发不出去,同样被踢。
快速确认线程卡在哪:
bash
jstack <pid> | grep -A 12 'consumer-thread'
第三步:看生产端是不是突然暴涨
如果消费端一切正常、速率也没降,那就是进来的量变多了。
bash
# 看各分区末端 offset,隔一分钟比一次,差值就是该分区写入速率
kafka-run-class.sh kafka.tools.GetOffsetShell \
--broker-list broker1:9092 --topic order-event
三个典型场景:
- 流量突增:大促、批量导入、上游补偿脚本一次性灌数据
- 重试风暴:上游消费失败不停重投,同一条消息反复进队列,看起来量翻倍
- 分区倾斜:只有两三个分区积压、其余都追平了。这不是总量问题,而是 key 分布不均 ------ 某个热点用户或热点商家全落到同一个分区
分区倾斜很好识别:--describe 里 LAG 集中在少数分区,就是它。
第四步:看分区数和 broker 有没有卡住
这一步最容易被忽略。Kafka 的并行度上限就是分区数:一个分区在同一个消费组里只能被一个消费者消费。
bash
kafka-topics.sh --bootstrap-server broker1:9092 --describe --topic order-event
重点看两处:
PartitionCount和消费者实例数的关系。20 个分区配 30 个实例,多出来的 10 个完全空闲,扩容没意义。Isr是否短于Replicas。ISR 收缩说明有副本跟不上,broker 侧磁盘 IO 或网络已经吃紧,此时消费速度也会被拖慢。
broker 侧再看三件事:磁盘 %util、网卡带宽、UnderReplicatedPartitions 指标。副本同步抢带宽时,消费拉取会明显变慢。
第五步:处置,先止血再治本
按优先级排:
bash
# 1. 扩容消费者(实例数不超过分区数),或单实例内起多个消费线程
# 每个线程持有独立 KafkaConsumer,不要多线程共享一个实例
# 2. 批量拉取,减少网络往返
# fetch.min.bytes=1048576 max.poll.records=500
# 3. 生产端限流 + 非核心 topic 降级,把带宽让给核心链路
# 4. 万不得已跳过堆积(会丢消息,必须确认可丢)
kafka-consumer-groups.sh --bootstrap-server broker1:9092 \
--group order-consumer --topic order-event \
--reset-offsets --to-latest --execute
治本的三件事:分区数按未来一年峰值预留、消费者实例数与分区数保持整数比、LAG 监控双阈值(条数 + 延迟时间)而不是只看单条告警。
避坑清单
- 别用
--reset-offsets --to-earliest当「修复」。它会让积压瞬间归零,但消息全被跳过,等于静默丢数据。 - 扩分区不是免费的。分区数一变,
key % 分区数的映射就变了,同一业务 key 会落到新分区,顺序保证被破坏。有顺序要求的 topic 宁愿扩消费者也不扩分区。 max.poll.records调大要同时调大max.poll.interval.ms,否则处理一批的时间超过上限,照样被踢出组。- 多线程消费必须每个线程一个
KafkaConsumer。KafkaConsumer不是线程安全的,共享会抛ConcurrentModificationException。 - 看 LAG 别看单个分区。总量追平了但某个分区一直不降,是那个分区有消费卡点或消费者分配不均。
- 消费者时间不要用
auto.offset.reset=latest兜底。新实例起来如果没提交过 offset,会直接从最新开始,前面全丢。 - 积压期间不要盲目重启。重启会让所有分区重新 rebalance,消费中断几十秒到几分钟,LAG 反而更高。
一图总结
- 第一步 看积压 :
--describe连看两次,LAG 不涨是旧账,持续涨才是故障 - 第二步 看消费 :组状态与
CONSUMER-ID,分清「没拉」还是「拉得慢」 - 第三步 看生产:分区末端 offset 增量,分清流量突增、重试风暴、key 倾斜
- 第四步 看瓶颈:消费者数 vs 分区数、ISR 是否收缩、broker IO 与带宽
- 第五步 看处置:扩消费者优先,扩分区慎用,跳过堆积等于丢数据
积压排查的核心不是盯 LAG 数字,而是回答一个问题:是进来的太快,还是出去的太慢,还是根本没出去。 三个方向分开看,答案就很清楚。