Kafka 消费积压排查:从 lag 曲线到 rebalance 风暴

举报
yd_282015185 发表于 2026/11/05 15:35:45 2026/11/05
【摘要】 lag 大不等于消费慢。阶跃上涨、线性增长、消费速率下降分别对应三种完全不同的根因,用错方向加十台机器也没用。本文按曲线形状、分区分布、位点提交三步排查,附八条命令清单和一份决策表。

## 一、lag 大不等于消费慢,先看它是怎么涨的


Kafka 消费积压(consumer lag)是所有消息队列问题里最容易被误判的一类。看到 lag 报警,绝大多数人的第一反应是"消费者不够,加机器"。但实际上 lag 上涨至少有四种完全不同的成因,其中只有一种能靠加机器解决。


判断的第一步不是看 lag 的绝对值,而是看它的**曲线形状**。三个问题必须问清楚:


- lag 是**阶跃式上涨**(某个时刻突然跳上去)还是**线性增长**(持续缓慢上涨)?

- 上涨期间**消费速率**(每秒消费条数)是掉了,还是没变?

- **生产速率**是否同步变化?


阶跃式上涨 + 消费速率正常,说明是某个消费者掉线后的 rebalance,或者某个分区卡住了。线性增长 + 消费速率正常,说明是生产端提速了,消费能力本身没退化。只有"消费速率明显下降 + lag 持续增长"才是真的消费能力不足。


这三种情况的处理方式完全不同:第一种要查 rebalance 和卡住的分区,第二种要么扩容要么限流生产端,第三种才需要加消费者。用错方向,加十台机器也没用。


## 二、lag 到底怎么算的,为什么你和监控看到的数字不一样


`kafka-consumer-groups.sh --describe` 输出的 LAG 是 `LOG-END-OFFSET - CURRENT-OFFSET`,这是**分区级别**的差值。汇总成 group 级别时,有人求和、有人取最大值,两者差别很大。


更关键的是两个坑:


**坑一:lag 是 offset 差值,不是消息条数。** 如果 topic 开了 compaction,或者有事务消息、控制批次,offset 的差值和实际未消费消息数并不相等。极端情况下 lag 显示几百万,实际待消费消息只有几万(大量 offset 被压缩掉了)。


**坑二:`--describe` 拿到的是某一瞬间的快照,不是速率。** 要算速率必须连续采样两次:


```bash

# 采样两次,间隔 10 秒,算真实消费速率

kafka-consumer-groups.sh --bootstrap-server broker1:9092 \

  --describe --group order-consumer | awk 'NR>1 && $6 ~ /^[0-9]+$/ {sum+=$6} END {print sum}'

sleep 10

kafka-consumer-groups.sh --bootstrap-server broker1:9092 \

  --describe --group order-consumer | awk 'NR>1 && $6 ~ /^[0-9]+$/ {sum+=$6} END {print sum}'

```


两次结果的差值除以 10,才是真实消费速率(条/秒)。同时用 `kafka-run-class.sh kafka.tools.GetOffsetShell` 或监控平台的 produce rate 拿到生产速率,两者对比才能判断是"消费变慢"还是"生产变快"。


## 三、消费速率掉了,先分清是消费者慢还是下游慢


消费者线程本身很少是瓶颈。真正的瓶颈通常在它调用的下游:


- **数据库写入慢**:批量 insert 变成逐条 insert,或者下游 DB 出现锁等待

- **同步 HTTP 调用**:消费逻辑里调了第三方接口,对方抖动直接把消费线程拖住

- **单条消息处理过重**:一条消息触发全量计算或大文件处理


判断方法很直接:看消费者的 **`poll` 间隔**。如果 `max.poll.interval.ms` 频繁超时,说明单批处理时间过长;如果没超时但吞吐上不去,基本可以确定是下游阻塞。


```bash

# 看消费者日志里的 rebalance 与 poll 超时

grep -E "Max poll interval|Rebalance|Attempt to heartbeat" consumer.log | tail -30

```


另一个高频原因是 **`max.poll.records` 设得太大**。默认 500 条,如果单条处理 10ms,一批就是 5 秒,再加上下游抖动,很容易突破 `max.poll.interval.ms`(默认 300 秒),触发 rebalance,rebalance 期间整个 group 停摆,lag 瞬间飙升,然后形成"越积压越 rebalance、越 rebalance 越积压"的正反馈。


**这是积压最常见的自我放大机制**,也是阶跃式 lag 上涨的头号原因。


## 四、rebalance 风暴:lag 阶跃上涨的真凶


rebalance 的触发条件有四种:


1. 消费者加入或离开 group(包括被判定死亡)

2. 订阅的 topic 分区数变化

3. 消费组 coordinator 变更

4. `max.poll.interval.ms` 超时被踢出


其中第 4 种最隐蔽,因为它会在**消费越慢的时候越容易触发**,形成正反馈。第 1 种则常见于容器环境:Pod 滚动更新、OOM 重启、健康检查失败,都会让消费者反复进出 group。


判断是不是 rebalance 风暴,看 coordinator 日志里的 `Preparing to rebalance group` 频率,以及 group 状态的 `state` 字段:


```bash

kafka-consumer-groups.sh --bootstrap-server broker1:9092 \

  --describe --group order-consumer --state

```


如果短时间内 state 反复在 `PreparingRebalance` / `CompletingRebalance` / `Stable` 之间跳,就是风暴。处理顺序是:


1. **先调大 `max.poll.interval.ms`**(比如 300000 → 600000),给慢消费留出时间

2. **调小 `max.poll.records`**(比如 500 → 100),让单批处理时间可控

3. **检查消费者是否被频繁重启**:`kubectl get pod -w` 看 restart 次数

4. 容器环境配合 `session.timeout.ms` 和 `heartbeat.interval.ms` 调整,避免 GC 停顿被误判死亡


## 五、分区分配不均:一半消费者在空转


`--describe` 输出里如果出现某些分区 LAG 几十万、另一些分区 LAG 为 0,就是分区分配不均。


常见原因有两个:


**原因一:分区数少于消费者数。** 5 个分区、8 个消费者,永远有 3 个消费者分不到分区,纯空转。这种情况加消费者毫无意义,必须先扩分区。


**原因二:range 分配策略导致倾斜。** `RangeAssignor` 在分区数不能整除时,前面的消费者会多拿一个分区。如果多拿的那个恰好是热点分区,那台消费者就会成为瓶颈。改成 `CooperativeStickyAssignor` 或 `RoundRobinAssignor` 可以缓解。


```properties

# 推荐:协作式粘性分配,减少 rebalance 影响范围

partition.assignment.strategy=org.apache.kafka.clients.consumer.CooperativeStickyAssignor

```


`CooperativeStickyAssignor` 相比老的 `StickyAssignor` 最大的好处是**支持增量 rebalance**,不必在 rebalance 期间全量停止消费,积压不会因为一次扩容而雪上加霜。


## 六、热点分区:key 设计的问题,加消费者治不了


即使分区数足够、分配也均匀,仍然可能出现单个分区 lag 远超其他分区。这通常是 **key 设计导致的数据倾斜**:比如用 `userId` 做 key,但有几个大客户的订单量占了 80%。


判断方法是看每个分区的 produce rate,而不是只看 lag:


```bash

kafka-run-class.sh kafka.tools.GetOffsetShell \

  --bootstrap-server broker1:9092 --topic order-events --time -1

sleep 30

kafka-run-class.sh kafka.tools.GetOffsetShell \

  --bootstrap-server broker1:9092 --topic order-events --time -1

```


两次差值就是各分区的生产速率。如果某个分区的速率是其他分区的十倍以上,就是热点分区。


热点分区的解法只有三个:**换 key(加随机后缀打散)、拆 topic、在消费端做二级并行**。加消费者对热点分区完全无效,因为一个分区只能被一个消费者消费。


## 七、消费位点提交方式:自动提交掩盖了真实进度


`enable.auto.commit=true` 是默认值,它按 `auto.commit.interval.ms`(默认 5 秒)提交。问题在于**自动提交提交的是 poll 到的位置,不是处理完的位置**。如果处理逻辑是异步的,或者处理完才落库,那么 consumer 的 committed offset 会"虚高",lag 显示正常但实际有大量消息没处理完。


反过来,如果处理失败但位点已提交,消息就永久丢失了。


排查时先确认提交方式:


```bash

kafka-consumer-groups.sh --bootstrap-server broker1:9092 \

  --describe --group order-consumer --members --verbose

```


生产环境建议一律手动提交,并且**处理完成后再提交**,配合幂等消费(业务唯一键 + 去重表)。这样才能保证 lag 反映真实进度。


## 八、八条命令清单


```bash

# 1. 看 group 整体 lag 与分区分布

kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group <group>


# 2. 看 group 状态(是否在 rebalance)

kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group <group> --state


# 3. 看成员与分配(谁分到了哪些分区)

kafka-consumer-groups.sh --bootstrap-server broker1:9092 --describe --group <group> --members --verbose


# 4. 看 topic 各分区最新位点(算生产速率)

kafka-run-class.sh kafka.tools.GetOffsetShell --bootstrap-server broker1:9092 --topic <topic> --time -1


# 5. 看 topic 各分区最早位点(算保留量)

kafka-run-class.sh kafka.tools.GetOffsetShell --bootstrap-server broker1:9092 --topic <topic> --time -2


# 6. 看消费者日志里的 rebalance / poll 超时

grep -E "Rebalance|Max poll|heartbeat" consumer.log | tail -50


# 7. 看分区副本是否同步(ISR 不足也会拖慢消费)

kafka-topics.sh --bootstrap-server broker1:9092 --describe --topic <topic>


# 8. 看消费者线程堆栈(确认卡在哪)

jstack <pid> | grep -A 20 "consumer-"

```


## 九、积压已经很大了,怎么安全地追上


lag 上百万时,"重启消费者"往往会让情况更糟:重启触发 rebalance,所有分区重新分配,消费从零开始预热,lag 继续上涨。


正确的追平顺序:


1. **先定位根因**,确认是 rebalance、下游慢还是热点分区,别盲目扩容

2. **临时提高并行度**:如果分区数够,加消费者实例;如果分区数不够,只能扩分区(注意扩分区会改变 key 的路由,可能引入新的倾斜)

3. **降低单批处理成本**:调小 `max.poll.records`,把批量写库改成真正的批量(`insert ... values (...),(...)`)

4. **必要时跳过历史消息**:`kafka-consumer-groups.sh --reset-offsets --to-latest --execute`,前提是业务允许丢历史

5. **追平后回归配置**:把临时调大的参数改回去,否则下次故障时更难判断


第 4 条要特别谨慎。跳过 offset 是不可逆的,必须先确认这些消息是否已通过其他途径补偿(比如落库的 dead letter 表),否则就是数据丢失。


## 十、决策表


| 现象 | 判断 | 处理 |

|---|---|---|

| lag 阶跃上涨,消费速率正常 | rebalance 或分区卡住 | 查 `--state`、查消费者重启次数 |

| lag 线性上涨,消费速率正常 | 生产端提速 | 扩容或限流生产端 |

| lag 上涨 + 消费速率下降 + 下游 RT 升高 | 下游阻塞 | 优化下游,别加消费者 |

| 部分分区 lag 高、部分为 0 | 分配不均或分区数不足 | 扩分区或换分配策略 |

| 单个分区 lag 远高于其他 | 热点分区(key 倾斜) | 换 key 或拆 topic,加消费者无效 |

| 频繁 `Max poll interval` 超时 | 单批处理过久 | 调小 `max.poll.records`,调大 `max.poll.interval.ms` |

| lag 显示正常但业务有未处理消息 | 自动提交掩盖进度 | 改手动提交,处理完再提交 |

| ISR 副本不足 | broker 侧问题 | 先修 broker,与消费者无关 |


一句话总结:**lag 是结果,不是原因。先看曲线形状和消费速率,再决定是加机器还是改配置——用错方向,加十台机器也只是让 rebalance 来得更热闹。**


【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。