一次 rebalance,全组停摆:Kafka lag 从 0 到百万的事故复盘

一次 rebalance,全组停摆:Kafka lag 从 0 到百万的事故复盘

周五 22:14,支付回调组 pay-notify 的 lag 从 0 开始爬,23:40 破百万。86 分钟里 broker 零故障、生产零失败------摁停整条管道的,是消费者组自己的 rebalance 风暴。案情为构造典型案例,参数按 Kafka 3.x 口径。

一、时间线:broker 全绿,组瘫了

背景:12 分区 topic、3 个消费者实例、单条处理约 200ms,全部默认参数(max.poll.records=500、max.poll.interval=5min、session.timeout=45s)【官方,Kafka 3.x 默认值】。当晚批处理把写入顶到 2000 条/s。

时间 事件 当时的判断
22:14 实例-2 Full GC 47s,心跳同停,超 session.timeout 被踢 无告警
22:15 整组 rebalance,全组停摆,lag 陡涨 误判一:MySQL 慢,扩连接池
22:17 实例-2 回归入组,再触发一轮 rebalance 误判二:重启消费者
22:21 幸存实例 poll 到异常消息,单批超 5 分钟被踢 误判三:加消费线程
22:26~23:00 陷入 rebalance → 超时 → 再 rebalance 死循环 ------
23:12 grep 日志关键词 + consumer-groups describe 定位
23:40 隔离异常消息、整组重启 恢复

算账:2000 条/s × 停摆累计约 8 分半 ≈ 102 万。百万 lag 不需要灾难,十来分钟全组停摆就够了。

那晚最贵的教训:lag 是体温,日志关键词才是病灶------三个误判全是拿体温计开药。

二、rebalance 是 stop-the-world:修一个成员,全组买单

我维护的学习库里,消费端配置表写得直白:新组用 cooperative-sticky,"rebalance 不再全组停摆"。反着读就是结论:默认的 eager 再平衡是全组停摆------成员交回分区、重新入组、重算分配,期间整组不消费【从业者判断:eager 协议细节为经验补充】。

rebalance 不是 bug,是自愈机制;问题在计费:一个成员出事,账单由全组停摆来付。触发面四个,各有日志指纹:

触发面 判定机制(默认值) 日志指纹
心跳超时 心跳由后台线程负责;GC 长停顿或网络抖动超 45s 判死 Attempt to heartbeat failed
poll 间隔超时 单批处理超 5 分钟没回到 poll,主动被踢 max.poll.interval.ms 超时被踢
成员进出 滚动发布属正常;反复进出多因宿主机负载、DNS 慢、CPU limit 打满 left group
分区再分配 / 协调者切换 扩分区触发重分配【从业者判断】;协调者 broker 抖动,组集体迁移 Rebalance 成群出现

两道闸门盯的不同:session.timeout 问"心跳还在吗",max.poll.interval 问"主循环还在转吗"。GC 停顿两道全踩。

连带账单:rebalance 时分区转给别的实例,原实例 commitSync 被拒(generation 已变,即 IllegalGeneration),新实例从旧位移接手,这批消息重复处理。at-least-once 下重复不可消除,业务侧用幂等键兜底。

rebalance 的代价不是"重新分配",是"全组不消费"。

三、雪崩链:一次 GC 停顿怎么演变成全组瘫痪

text 复制代码
Full GC 47s(实例-2)
  → 心跳停超 session.timeout=45s → 被踢
  → 整组 rebalance:全组停摆,lag 陡涨
  → 实例-2 回归 → 再一轮 rebalance
  → 幸存者接管更多分区,撞上毒丸
  → 单批超 max.poll.interval=5min → 又被踢 → 循环

雪崩要两个放大器。其一,stop-the-world:被踢一台,停摆全组。其二,重入:GC 结束的实例不是悄悄归队,是触发下一轮 rebalance------排障表点名的宿主机负载、DNS 慢、CPU limit 打满,都是同一模式:一个不稳定成员,全组反复付账。

更糟的是正反馈:毒丸不挑宿主,每轮 rebalance 把分区连同毒丸一起换人接手;组里人越少、摊到的分区越多,下一个撞上的概率越高。故障中的组会变得更脆弱------这不是线性恶化,是自我加强。

四、poison pill:一条消息卡死一个组

那条 22:21 的异常消息:新版改了字段结构,旧逻辑反序列化抛异常,原地重试。offset 推不过它,每次 rebalance 后新接管的实例从旧位移拉起,再撞一次。一条消息,让一个组无限次重启自己。

消费停滞告警的注释里有个词组:"rebalance 死锁/下游卡死"。毒丸正是两病合体:处理卡死导致 poll 超时,poll 超时导致 rebalance,rebalance 让毒丸换宿主再来一遍。

末端处置与源头判断【从业者判断】:单条 catch、失败消息进死信 topic、offset 照常推进;源头治理更划算------毒丸最常见来源是 schema 不兼容,两条纪律直接可用:加字段配默认值、删字段三思(Registry 默认 BACKWARD)。

毒丸的杀伤不在消息本身,在于它让 offset 永远推不过去。

五、缓解:顺序比清单重要

第一步修 GC。排障表里的原话:"修 GC(堆/算法)、查网络重传"。参数救不了停顿 47 秒的 JVM------参数是止血带,不是疫苗。

第二步配置卫生。公式原样照抄:"单批处理耗时 = 条数 × 单条耗时,必须 < max.poll.interval.ms"。500 条 × 200ms = 100s 看着安全;下游一抖单条 700ms,单批 350s,就越过红线了。方向:调小 max.poll.records、调大 max.poll.interval.ms,两者配合,慢作业再异步化。

properties 复制代码
# 消费端缓解四件套(数值注释为 Kafka 3.x 默认值)
max.poll.records=100          # 500
max.poll.interval.ms=600000   # 300000
session.timeout.ms=45000      # 45000;先修GC再动它
partition.assignment.strategies=org.apache.kafka.clients.consumer.CooperativeStickyAssignor
group.instance.id=pay-notify-0  # 静态成员:每实例固定一个

第三步机制两件套。static membership(group.instance.id):滚动发布不触发 rebalance,约束是重启窗口必须落在 session.timeout 内。

cooperative-sticky:增量再平衡只挪必须挪的分区,"rebalance 不再全组停摆";3.x 默认列表是 RangeAssignor, CooperativeStickyAssignor,要增量语义就显式只配后者,存量组迁移需全员同步换【从业者判断】。

session.timeout 取舍【从业者判断】:调大抗抖动、误踢少,代价是真宕机的分区接管变慢;调小反之。在线业务通常更怕误踢连锁------宁可检测慢些,也要把 GC 修到停顿远小于 45s。

static membership 治发布,cooperative-sticky 治停摆,修 GC 治根本------顺序反了都是白折腾。

六、lag 监控的正确口径:增速,不是绝对值

一道自测题:组 A lag 常年 5 万不涨,组 B 只有 200 但每分钟涨 5 万,谁该告警?B------A 收支平衡,B 消费停滞。绝对值必须换算成消化时间:"10 万条对 5 万条/秒的组是 2 秒的事,对 10 条/秒的组是灾难。"锚点取「15 分钟可消化的量」,别抄整数。

kafka_exporter 不直接吐 lag,用两条同标签指标相减;增速看 rate(kafka_consumergroup_current_offset5m)。落到 PrometheusRule:

yaml 复制代码
# [master] kubectl apply -f kafka-lag-rules.yaml(骨架,expr 上线前先实测)
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: kafka-lag-rules
  namespace: monitoring
  labels:
    release: prometheus   # operator 靠它选规则,写错是"不生效"头号原因
spec:
  groups:
    - name: kafka-lag
      rules:
        # lag 绝对值:warning,阈值按消化时间换算,10 万仅格式示例
        - alert: KafkaConsumerLagHigh
          expr: kafka_consumergroup_log_offset - kafka_consumergroup_current_offset > 100000
          for: 10m
          labels: {severity: warning}
        # 消费停滞:LEO 在涨、位移不动,比绝对值更早发现
        - alert: KafkaConsumerStalled
          expr: >
            (increase(kafka_consumergroup_log_offset[15m]) > 100)
            unless (increase(kafka_consumergroup_current_offset[15m]) > 0)
          for: 15m
          labels: {severity: critical}
        # 另一条线:under-replicated 与 broker 数也配 critical,完整四条见文末学习库

拿本案套一遍:第一轮死循环里,停滞告警就该升 critical------它认的是「LEO 在涨、位移不动」这个事实,不赌 lag 阈值换算得对不对。

第三道防线放日志【从业者判断】:Rebalance、Attempt to heartbeat failed、left group、IllegalGeneration 四个指纹配进日志告警------火苗在 lag 烧起来之前就能看到。

bash 复制代码
# [任意节点] 组的实时 lag(只读)
/opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
  --describe --group pay-notify
# 预期:12 行,LAG 列合计即全组积压

# 消费端日志三关键词
grep -E "Attempt to heartbeat failed|max.poll.interval|IllegalGeneration" app.log | tail -20
# 预期:heartbeat failed→GC/网络;max.poll.interval→处理慢;IllegalGeneration→被踢后还在提交

lag 的绝对值是身高,增速才是心电图。

七、预答三个反方

"session.timeout 调到两分钟不就完了?"误踢变少,但真宕机的接管也要等两分钟。47 秒的 GC 是病,阈值只是裤腰带。

"lag 绝对值告警简单直接。"常见坑表第一行就是下场:阈值 10 万、天天误报,两周后被全员静音。

"重启消费者最省事。"重启即成员进出,即再触发一轮 rebalance------递柴火。先 grep 分清离组类型,再下药。

八、30 秒自检

检查项 过关标准
max.poll.records × 单条耗时 显著小于 max.poll.interval.ms
GC 停顿 P99 远小于 45s,否则随时被误踢
滚动发布 已配 group.instance.id,重启窗口 < session.timeout
分配策略 显式 CooperativeStickyAssignor
告警 有速率/停滞告警,绝对值按消化时间换算

现在就能做的三件事:

  1. grep 那三个关键词,看 24 小时内有没有被静默踢组;
  2. 用公式算最慢的组,离 5 分钟红线还有多远;
  3. 跑一遍 consumer-groups --describe,把"lag 高但不涨"的组从 critical 里摘出来。

你最近一次半夜被 lag 告警叫醒,查明是四个触发面里的哪个?评论区对号入座。

写在最后

复盘会上有人问:Kafka 不靠谱?反了。broker 86 分钟零故障,说明协议在忠实执行;不可靠的是交给它的成员------一台 GC 失控的 JVM、一条没人管的毒丸、一组没人算过的默认参数。rebalance 风暴不是 Kafka 的故障,是它对"你不健康"的如实汇报。

时间线、配置清单与告警骨架整理在我维护的开源学习库------GitHub 搜 sre-learning-hub,Kafka 章节实验全部可在 Docker 环境复现;告警可直接抄,expr 上线前先在自己的 Prometheus 里查一次。

相关推荐
Sashtok1 小时前
cpp基础知识(6)
后端
用户2145549310151 小时前
VeapAI 自然语言操作实战|第 02 篇:语义层——把系统讲给模型听的四个构件
前端·后端
斑鸠喳喳1 小时前
期货模式 FutureTask
后端
badhope1 小时前
AI Agent安全治理实战:从API密钥泄露到沙箱隔离的完整防线
后端·llm·agent
武子康1 小时前
旧工具结果写进新回合:两次消费之间的打断窗口
人工智能·后端·agent
anew___1 小时前
《从零手写操作系统 (48):网络协议栈基础——Socket API与TCP/IP分层架构》
网络协议·tcp/ip·架构
weixin_382395232 小时前
0-500 人工厂的生产 PMC 系统怎么做?基于 Tauri + SQLite 的性能架构实践与 5000 物料压测实录
数据库·架构·sqlite
许雪里2 小时前
Agent 记忆架构设计:原理 × 业界三大范式 × XXL-AI 落地实践
后端·llm·agent
软件工程师文艺2 小时前
制造业数字化转型路线图:从信息化总体规划到智能工厂的五阶段演进
架构