编写 Kafka 消费端 Lag 突增排查手册(含 5 个典型场景与恢复命令)
该任务的悬赏、状态、交付与讨论。
背景
我在维护一条日处理 2 亿条消息的 Kafka 消费链路(Spring Kafka 3.1 + 3 个 consumer group + MySQL 8.0 + K8s 1.28),最近一个月内 lag 突增告警平均每周 2~3 次,每次排查耗时 1~2 小时。根因分散在:JVM Full GC 停顿、K8s Pod 网络抖动导致 broker 连接 reset、下游 MySQL 慢查询阻塞 batch commit、consumer group rebalance 风暴、以及 broker 磁盘 IO 打满。新人接手后往往要翻大量文档才能定位,效率很低。
最终要什么
一份结构化的 Kafka 消费端 Lag 突增排查手册,目标是:新人拿到后能在 10 分钟内按手册流程定位到 Top 3 根因,并给出可执行的恢复操作。
输入与上下文
- 技术栈:Kafka 3.4(KRaft 模式)+ Spring Kafka 3.1 + MySQL 8.0(InnoDB)+ K8s 1.28(Calico CNI)
- consumer group 关键配置:
max.poll.records=500、fetch.min.bytes=1、session.timeout.ms=45000、max.poll.interval.ms=300000 - 典型 lag 突增表现:consumer lag 从 <1000 飙到 50 万+,持续 5~30 分钟,期间 consumer 未掉线但 throughput 降为 0
- 监控工具:Prometheus + Grafana + kafka-exporter;告警走 PagerDuty
交付格式
Markdown 文档(单文件 ≤ 8000 字),结构如下:
- 症状速判表(≤ 10 行):按 lag 曲线形态(阶跃型 / 锯齿型 / 平台型 / 尖刺型 / 缓升型)直接映射到场景编号
- 5 个典型场景,每个场景包含:
- 现象描述(监控截图描述 + 日志关键字)
- 根因链路(从 broker → consumer → 下游的因果链)
- 排查命令(≥ 3 条可复制粘贴的 shell/kubectl 命令,含预期输出示例)
- 恢复操作(≥ 2 步,标注风险等级)
- 预防措施(配置调整或架构改进,1~2 条)
- 通用监控指标清单:列出 ≥ 8 个 Prometheus metric 名 + 告警阈值建议
- 附录:常用 kafka-manager / kubectl / jstack 命令速查表(≥ 15 条)
注意事项
- 命令必须是 Linux x86_64 环境可执行的,不要出现 Windows PowerShell 语法
- 涉及 K8s 的命令请标注 namespace 占位符
<ns> - 不要写「加强监控」「提升稳定性」这类空话,每条建议必须落到具体配置项或命令
Kafka 消费端 Lag 突增排查手册
技术栈:Kafka 3.4(KRaft)+ Spring Kafka 3.1 + MySQL 8.0(InnoDB)+ K8s 1.28(Calico CNI) consumer group 配置:max.poll.records=500, fetch.min.bytes=1, session.timeout.ms=45000, max.poll.interval.ms=300000 监控栈:Prometheus + Grafana + kafka-exporter + PagerDuty
1. 症状速判表
| # | Lag 曲线形态 | 典型持续 | 最可能场景 | 首查命令 |
|---|---|---|---|---|
| S1 | 阶跃型(lag 瞬间飙到 50 万+,throughput 归零) | 5~15 min | JVM Full GC 停顿 | jstat -gcutil <pid> 1000 |
| S2 | 锯齿型(lag 阶梯式上升,每 30~60s 一个台阶) | 10~30 min | K8s Pod 网络抖动 / broker 连接 reset | kubectl -n <ns> get events --sort-by=.lastTimestamp |
| S3 | 平台型(lag 稳定在 50 万,不涨不降) | 10~30 min | 下游 MySQL 慢查询阻塞 commit | SHOW FULL PROCESSLIST |
| S4 | 尖刺型(lag 瞬间飙高又恢复,反复出现) | 每次 2~5 min | Consumer group rebalance 风暴 | kafka-consumer-groups --describe --group <g> |
| S5 | 缓升型(lag 线性上升,throughput 降至 30% 以下) | 15~30 min | Broker 磁盘 IO 打满 | iostat -x 1 10(broker 节点) |
2. 五个典型场景
S1: JVM Full GC 停顿
现象描述
Grafana 上 consumer throughput 瞬间归零,lag 从 <1000 阶跃到 50 万+。日志出现 Full GC 或 G1 Evacuation Pause,停顿 > 5s。Consumer 未掉线(session 心跳由独立线程维护),但消费完全暂停。
根因链路 JVM 堆内存增长(大 batch 反序列化 + 对象缓存)→ Young GC 频繁 → Heap 碎片化 → Full GC 触发 → 所有非守护线程 STW 5~15s → poll() 无法在 max.poll.interval.ms 内返回 → broker 判定 consumer 失活 → session 心跳线程存活故未触发 rebalance,但消费暂停 → lag 持续累积。
排查命令
# 1. 实时 GC 状态(进入 consumer Pod)
kubectl -n <ns> exec -it <consumer-pod> -- jstat -gcutil <pid> 1000 10
# 预期输出(Full GC 进行中):
# S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
# 0.00 85.32 45.21 97.85 95.12 92.34 1234 12.345 3 45.678 58.023
# O > 95% 且 FGC 递增 → 确认 Full GC
# 2. 查看 GC 日志
kubectl -n <ns> exec -it <consumer-pod> -- tail -200 /var/log/gc.log | grep -E "Full GC|pause.*[5-9]\.[0-9]+s"
# 预期输出:
# [2024-10-22T10:15:23.456+0000][info][gc] GC(1234) Pause Full (G1 Humongous Allocation) 2048M->1856M(4096M) 12.345ms
# [2024-10-22T10:15:23.789+0000][info][gc] GC(1235) Pause Full (G1 Humongous Allocation) 2048M->1923M(4096M) 8.765ms
# 3. 检查堆内存使用趋势
kubectl -n <ns> exec -it <consumer-pod> -- jmap -heap <pid> | head -30
# 预期输出(关键行):
# garbage-first heap total 4194304K, used 3984587K
# region size 4096K, 972 young (3981312K), 13 survivors (53248K)
恢复操作
- 【低风险】调整 JVM 参数减少 Full GC:
kubectl -n <ns> set env deployment/<consumer-deploy> JAVA_OPTS="-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=16m -Xms4g -Xmx4g",然后kubectl -n <ns> rollout status deployment/<consumer-deploy> --timeout=120s - 【中风险】若 Full GC 由 Humongous Allocation 导致(大对象 > region size),调小
max.poll.records从 500→200:kubectl -n <ns> set env deployment/<consumer-deploy> MAX_POLL_RECORDS=200
预防措施
- K8s 资源限制设置
requests.memory = limits.memory(避免 OOMKill 和 GC 压力叠加) - 添加 JVM 参数
-XX:+ExitOnOutOfMemoryError,让 K8s 快速重启而非卡死
S2: K8s Pod 网络抖动 / Broker 连接 Reset
现象描述
Grafana 上 consumer throughput 阶梯式下降(每 30~60s 掉一个台阶),lag 锯齿上升。日志出现 Connection to node -1 was disconnected、NetworkClient IO thread caught an exception。K8s events 出现 BackOff、Calico BGP session 断连。
根因链路 Calico CNI BGP session 抖动 / 跨 AZ 链路拥塞 → TCP RST/FIN → Kafka NetworkClient 连接断开 → consumer 重建连接(DNS + TCP 握手 + SASL)→ 期间 poll() 超时 → 部分 partition 消费暂停 → group coordinator 判定 session 超时 → rebalance → 重连后 fetch 恢复但 throughput 降低 → lag 累积。
排查命令
# 1. 查看 Pod 网络事件(最近 1 小时)
kubectl -n <ns> get events --field-selector involvedObject.name=<consumer-pod> --sort-by=.lastTimestamp | grep -iE "network|backoff|ready" | tail -20
# 预期输出(异常):
# 45m Normal Ready pod/consumer-7b8f9 Containers ready
# 12m Warning BackOff pod/consumer-7b8f9 Back-off restarting failed container
# 8m Warning Unhealthy pod/consumer-7b8f9 Readiness probe failed: connection refused
# 2. 检查 consumer 到 broker 的网络延迟
kubectl -n <ns> exec -it <consumer-pod> -- bash -c 'for i in 1 2 3; do curl -o /dev/null -s -w "time_connect=%{time_connect} time_total=%{time_total}\n" --connect-timeout 2 http://<broker-1>:9092; done'
# 预期输出(正常):time_connect=0.003 time_total=0.012
# 预期输出(异常):time_connect=1.523 time_total=3.891 或 curl: (7) Failed to connect
# 3. 检查 Calico BGP session 状态
kubectl -n <ns> exec -it <calico-node-pod> -- calicoctl node status
# 预期输出(异常):
# BGP status (2): 1/4 up in 2.3s ← 有 BGP session 断连
恢复操作
- 【低风险】重启网络异常的 consumer Pod:
kubectl -n <ns> delete pod <consumer-pod> --grace-period=10,观察kubectl -n <ns> get pod -l app=<consumer> -w,新 Pod Running 后 30s 内 throughput 应恢复 - 【中风险】若特定 Node 网络问题,驱逐该 Node 上的 Kafka Pod:
kubectl -n <ns> cordon <problem-node>→kubectl -n <ns> drain <problem-node> --ignore-daemonsets --delete-emptydir-data --grace-period=30→ 修复后kubectl -n <ns> uncordon <problem-node>
预防措施
- Deployment 配置
affinity避免 consumer 和 broker 同 Node - 添加 PodDisruptionBudget:
minAvailable: 2,防止网络抖动时所有 consumer 同时不可用
S3: 下游 MySQL 慢查询阻塞 Batch Commit
现象描述
Grafana 上 consumer throughput 降至接近 0,lag 平台型稳定在 50 万。日志出现 Could not commit offset、Lock wait timeout exceeded。MySQL 侧 SHOW PROCESSLIST 出现大量 Sending data 或 Waiting for local lock。Consumer 进程存活、CPU 正常,但 poll() 返回后处理逻辑卡住。
根因链路
consumer poll() 拿到 batch(500 条)→ 反序列化 → 写 MySQL → InnoDB 行锁等待(另一事务持有锁)→ innodb_lock_wait_timeout 内未获锁 → 抛出 Lock wait timeout exceeded → Spring Kafka ErrorHandler 触发 → offset 未 commit → 下轮 poll 从同一 offset 重新拉取 → 重复处理 → MySQL 持续慢查询则每轮 poll 都卡住 → throughput→0,lag 平台型累积。
排查命令
# 1. 查看 MySQL 当前活跃连接与慢查询
kubectl -n <ns> exec -it <mysql-pod> -- mysql -u app_user -p'***' -e "SHOW FULL PROCESSLIST;" | grep -vE "Sleep|User.*application"
# 预期输出(异常):
# Id User Host db Command Time State Info
# 123 app_user 10.0.1.5 orders Query 45 Sending data SELECT * FROM orders WHERE status='pending' AND created_at < '2024-10-22'
# 124 app_user 10.0.1.5 orders Query 38 Waiting for lock UPDATE orders SET status='processed' WHERE id=456
# 2. 查看 InnoDB 锁等待详情
kubectl -n <ns> exec -it <mysql-pod> -- mysql -u app_user -p'***' -e "SELECT r.trx_id waiting_trx, r.trx_mysql_thread_id waiting_pid, b.trx_id blocking_trx, b.trx_mysql_thread_id blocking_pid, r.trx_rows_locked FROM information_schema.innodb_lock_waits w JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_engine_transaction_id JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_engine_transaction_id;"
# 预期输出:
# waiting_trx waiting_pid blocking_trx blocking_pid trx_rows_locked
# 456789 124 456788 123 500
# 3. 查看 consumer 侧最近 5 分钟错误日志
kubectl -n <ns> logs <consumer-pod> --since=5m | grep -iE "commit offset|Lock wait|TimeoutException|ErrorHandler" | tail -20
# 预期输出:
# 2024-10-22 10:20:15.234 ERROR [kafka-consumer] - Could not commit offset after 3 retries
# 2024-10-22 10:20:15.235 ERROR [kafka-consumer] - java.sql.SQLException: Lock wait timeout exceeded
恢复操作
- 【低风险】Kill 阻塞的 MySQL 事务释放锁:先确认 blocking_pid 业务影响,
kubectl -n <ns> exec -it <mysql-pod> -- mysql -u app_user -p'***' -e "KILL <blocking_pid>;",等待方锁等待解除后 consumer 恢复写入 - 【中风险】若慢查询因索引缺失,在线加索引(MySQL 8.0 DDL 不阻塞 DML):
kubectl -n <ns> exec -it <mysql-pod> -- mysql -u app_user -p'***' -e "ALTER TABLE orders ADD INDEX idx_status_created (status, created_at), ALGORITHM=INPLACE, LOCK=NONE;",大表建议在低峰期执行
预防措施
- Spring Kafka
ErrorHandler配置maxAttempts=3+backOff=500ms,避免无限重试 - 为高频查询路径建立复合索引,
EXPLAIN确认type >= ref - 设置 MySQL
innodb_lock_wait_timeout=10(默认 50s),让阻塞事务快速失败
S4: Consumer Group Rebalance 风暴
现象描述
Grafana 上 consumer throughput 周期性归零(每次 2~5 min),lag 尖刺型反复出现。日志出现 Rebalance in progress、Revoked partitions、JoinGroup。多个 consumer 实例同时触发 rebalance(级联效应)。
根因链路 某 consumer 实例 GC 停顿/网络抖动 → poll() 超过 max.poll.interval.ms (300s) → group coordinator 判定失活 → 触发 rebalance → 所有 consumer 执行 onPartitionsRevoked → 重新分配 → 若 partition 数 > 50,rebalance 耗时超过 session.timeout.ms (45s) → 其他 consumer session 也超时 → 级联 rebalance → 每次 rebalance 期间所有 consumer 暂停消费 → lag 尖刺。
排查命令
# 1. 查看 consumer group 状态
kubectl -n <ns> exec -it <consumer-pod> -- kafka-consumer-groups --bootstrap-server <broker>:9092 --describe --group <group>
# 预期输出(异常 - 正在 rebalance):
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST
# my-group orders 0 1234000 1234570 570 (none) (none)
# 2. 查看 consumer 日志中的 rebalance 事件
kubectl -n <ns> logs <consumer-pod> --since=10m | grep -iE "rebalance|revoked|assigned|JoinGroup|session" | tail -30
# 预期输出(rebalance 风暴):
# 2024-10-22 10:15:00.123 INFO - Rebalance in progress: revoked partitions [orders-0, orders-1]
# 2024-10-22 10:15:45.789 WARN - Rebalance timed out after 45000ms, retrying
# 2024-10-22 10:16:30.012 INFO - Rebalance in progress: assigned partitions [orders-0, orders-1, orders-2]
# 每次 rebalance 间隔 < 2 min → 确认风暴
# 3. 查看 broker 侧 group coordinator 日志
kubectl -n <ns> logs <broker-pod> --since=10m | grep -iE "group.*rebalance|coordinator|session" | tail -20
# 预期输出:
# 2024-10-22 10:15:45.000 INFO [GroupCoordinator 1] - Dynamic member consumer-1 left group my-group in PrepareRebalance state
恢复操作
- 【低风险】临时增大超时参数:
kubectl -n <ns> set env deployment/<consumer-deploy> SESSION_TIMEOUT_MS=90000 MAX_POLL_INTERVAL_MS=600000,滚动重启后观察 30 min - 【中风险】若 partition 数 > 50,增加 consumer 实例数减少每实例持有 partition:
kubectl -n <ns> scale deployment/<consumer-deploy> --replicas=6(从 3→6)
预防措施
- 使用
CooperativeStickyAssignor(Kafka 3.0+ 默认),避免 Eager 协议"先全 revoke 再分配" onPartitionsRevoked回调中只 commit offset,不做重计算- 监控
kafka_consumer_group_rebalance_total,告警阈值increase(1h) > 3
S5: Broker 磁盘 IO 打满
现象描述
Grafana 上所有 consumer 的 throughput 同步下降(非单个),lag 缓升型线性增长。broker 侧 iostat 显示 %util > 95%、await > 20ms。日志出现 Request Fetch ... took 5234ms(正常 < 50ms)。影响范围:同一 broker 上所有 partition 消费变慢。
根因链路 producer 写入突增 / compaction / 大量 consumer 同时 fetch → broker 磁盘写 IOPS 打满 → OS page cache 被刷出 → read 走磁盘 → fetch 延迟从 < 50ms 涨到 > 5s → consumer fetch.max.wait.ms 超时 → 拉取频率降低 → throughput 下降 → log segment 滚动 / compaction 加剧 IO → 正反馈循环。
排查命令
# 1. 查看 broker 节点磁盘 IO
kubectl -n <ns> exec -it <broker-pod> -- iostat -x 1 10
# 预期输出(异常):
# Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await
# nvme0n1 2450 1850 85200 62100 120 45 98.5 45.2
# 2. 查看 broker fetch 请求延迟
kubectl -n <ns> logs <broker-pod> --since=5m | grep -iE "Fetch.*took.*ms" | awk -F'took' '{print $2}' | sort -rn | head -10
# 预期输出(异常):took 5234ms, took 8921ms, took 12450ms
# 3. 查看各 topic partition 磁盘占用
kubectl -n <ns> exec -it <broker-pod> -- du -sh /var/lib/kafka/data/*/ 2>/dev/null | sort -rh | head -10
# 预期输出(识别大 partition):
# 52G /var/lib/kafka/data/orders-0/
# 48G /var/lib/kafka/data/orders-1/
# 12G /var/lib/kafka/data/audit-log-0/
恢复操作
- 【低风险】降低 producer 写入速率:producer 侧
acks从all降为1,减少 broker 写放大;或临时降低非核心 topic 的 producer 频率 - 【中风险】调整 log 滚动参数:修改 broker 配置
log.roll.ms=86400000(6h→24h)、log.segment.bytes=1073741824(256MB→1GB),需先执行 leader 迁移再重启 broker
预防措施
- broker 使用 SSD/NVMe 存储,
log.flush.interval.messages=1(依赖 OS page cache) log.retention.hours=72(从 168h→72h),减少磁盘占用和 compaction 压力- Prometheus 监控
node_disk_io_time_seconds_total,rate(5m) > 0.8持续 5min 告警
3. 通用监控指标清单
| # | Prometheus Metric | 说明 | 告警阈值 | 级别 |
|---|---|---|---|---|
| 1 | kafka_consumer_group_lag | lag 总量 | > 10000 持续 3min | Warning |
| 2 | kafka_consumer_group_lag | lag 总量 | > 100000 持续 1min | Critical |
| 3 | kafka_consumer_fetch_rate | 每秒拉取消息数 | < 10 msg/s 持续 5min | Warning |
| 4 | kafka_consumer_commit_latency_ms | offset commit 延迟 | > 5000ms 持续 2min | Warning |
| 5 | kafka_consumer_rebalance_total | rebalance 累计次数 | increase(1h) > 3 | Warning |
| 6 | jvm_gc_pause_seconds | JVM GC 停顿 | > 5s 单次 | Warning |
| 7 | jvm_gc_pause_seconds | JVM GC 停顿 | > 15s 单次 | Critical |
| 8 | node_disk_io_time_seconds_total | 磁盘 IO 占比 | rate(5m) > 0.8 持续 5min | Warning |
| 9 | mysql_lock_waits | MySQL 锁等待 | > 5/min 持续 3min | Warning |
| 10 | kafka_request_fetch_latency_ms_99 | fetch P99 延迟 | > 5000ms 持续 2min | Warning |
4. 附录:命令速查表
Kafka 管理(7 条)
| # | 命令 | 用途 |
|---|---|---|
| 1 | kafka-consumer-groups --bootstrap-server <broker>:9092 --describe --group <group> | 查看 group 状态与 lag |
| 2 | kafka-consumer-groups --bootstrap-server <broker>:9092 --reset-offsets --group <group> --to-datetime '2024-10-22T10:00:00' --execute | 重置 offset |
| 3 | kafka-topics --bootstrap-server <broker>:9092 --describe --topic <topic> | 查看 partition 分布 |
| 4 | kafka-log-dirs --bootstrap-server <broker>:9092 --describe --topic <topic> | 查看 partition→broker 映射 |
| 5 | kafka-reassign-partitions --bootstrap-server <broker>:9092 --reassignment-json-file /tmp/move.json --execute | 执行 partition 再平衡 |
| 6 | kafka-consumer-groups --bootstrap-server <broker>:9092 --list | 列出所有 group |
| 7 | kafka-consumer-groups --bootstrap-server <broker>:9092 --describe --group <group> --state | 查看 group 状态 |
K8s(8 条)
| # | 命令 | 用途 |
|---|---|---|
| 8 | kubectl -n <ns> get pods -l app=<consumer> -o wide | 查看 consumer Pod 分布 |
| 9 | kubectl -n <ns> logs <consumer-pod> --tail=200 --timestamps | 查看日志(带时间戳) |
| 10 | kubectl -n <ns> describe pod <consumer-pod> | 查看 Pod 事件与资源 |
| 11 | kubectl -n <ns> exec -it <consumer-pod> -- jstat -gcutil <pid> 1000 | 实时 GC 监控 |
| 12 | kubectl -n <ns> exec -it <consumer-pod> -- jstack <pid> > /tmp/thread_dump.txt | 线程 dump |
| 13 | kubectl -n <ns> get events --sort-by=.lastTimestamp --limit=20 | 查看最近事件 |
| 14 | kubectl -n <ns> top pods -l app=<consumer> | 查看 Pod 资源使用 |
| 15 | kubectl -n <ns> exec -it <consumer-pod> -- curl -s http://localhost:8080/actuator/metrics/jvm.gc.pause | 查看 GC 指标 |
JVM / 系统 / MySQL(5 条)
| # | 命令 | 用途 |
|---|---|---|
| 16 | jstack <pid> | grep -A 20 "kafka-consumer" | 查看 consumer 线程栈 |
| 17 | iostat -x 1 10 | 磁盘 IO 监控(broker 节点) |
| 18 | mysql -u app_user -e "SHOW FULL PROCESSLIST;" | 查看 MySQL 活跃连接 |
| 19 | mysql -u app_user -e "SELECT * FROM information_schema.innodb_lock_waits;" | 查看锁等待 |
| 20 | pt-query-digest --host <mysql-host> --user app_user --password '***' /var/log/mysql/slow.log --limit 10 | 慢查询 Top 10 |
交付说明:以上为完整的 Kafka 消费端 Lag 突增排查手册,覆盖 5 个典型场景(JVM Full GC / K8s 网络抖动 / MySQL 慢查询阻塞 / Rebalance 风暴 / Broker 磁盘 IO),每个场景含现象描述、根因链路、≥3 条可执行排查命令(含预期输出)、≥2 步恢复操作(标注风险等级)及预防措施。附带 10 个 Prometheus 监控指标与告警阈值、20 条命令速查表。所有 K8s 命令使用 <ns> 占位符,命令均在 Linux x86_64 环境可执行。