
1. Kafka 这只“千里眼”到底在看什么做后端这些年只要一提到 Kafka十个人里有九个半先聊吞吐、聊分区数、聊副本倍数剩下半个才会追问一句出问题的时候你怎么知道我印象最深的一次线上事故某个核心主题积压了几百万条消息从开始积压到业务方打电话过来中间隔了将近四十分钟我们的监控大盘上一片绿色一条告警都没响。后来复盘才发现大盘上只画了 broker 的 CPU 和磁盘使用率消费组的 Lag 压根没人采。从那天起我才真正开始研究 Kafka 里的这只“千里眼”——它不是某个开关也不是装个面板就完事而是一整套把集群内部状态翻译成人能看懂的语言的体系包括采什么、怎么采、采回来怎么判断、判断完怎么通知到人。这篇文章想聊的就是这套东西。如果你刚开始接触 Kafka只会用命令行看一眼--describe或者你已经有一块大盘但告警天天狼来了团队已经开始屏蔽告警群那这篇应该都能对上你的痛点。我会从观测面怎么划分讲起讲到指标采集的具体配置、Lag 这个数字背后的三个陷阱、用时间维度重算延迟的可跑脚本最后给一份排查速查表和上线前自查清单。全部是我在实际项目里验证过的东西能直接抄作业。1.1 四层观测面从集群心跳到业务链路很多人搭 Kafka 监控第一反应是找一块现成的 Grafana 模板导进去导完看着花花绿绿几十个图觉得齐活了。但模板是别人按别人的业务场景设计的你不知道每个图对应什么故障模式真出事的时候根本不知道该看哪一块。我的做法是先按“观测面”把 Kafka 拆成四层每一层回答一个独立的问题然后再去这四层里找对应的指标。集群层回答的是“这套 Kafka 还活着吗”。核心就几个问题控制器是不是唯一的、有没有分区掉线、ISR 是不是在持续收缩、broker 之间的请求队列有没有堆积。这一层的指标数量最少但优先级最高因为它一旦出问题上面三层全都是噪音。主题与分区层回答的是“数据进来得顺不顺”。写入速率、字节吞吐、分区之间是否倾斜、日志段滚动频率、单分区磁盘占用增长速度都属于这一层。我见过一个特别典型的情况某个主题的分区数设得太少写入端又用了按用户 ID 哈希的 key结果某几个分区常年比其他分区大五六倍日志段滚动频繁最后是磁盘先扛不住。消费组层回答的是“数据出去得顺不顺”也就是大家最熟悉的 Lag。但这一层远不止 Lag 一个数还包括消费组成员数变化、重平衡发生次数、offset 提交频率、每个分区被分配给谁。很多“Lag 突然飙升”的根因其实在这一层的重平衡上。业务链路层回答的是“这条消息到底有没有产生价值”。消息从生产到被业务逻辑处理完花了多久、有没有进死信、重试了几次、消息体大小是不是突然翻倍。这一层严格说不属于 Kafka 自身但它才是老板真正关心的一层也是最容易被忽略的一层。我的经验是这四层的告警优先级要明确分开。集群层出事直接电话分区层发工单消费组层进值班群业务链路层交给业务方自己盯。混在一起发结果就是所有告警都被当成噪音。1.2 原生工具链看得见什么、看不见什么Kafka 自带的那套命令行工具其实已经能看见很多东西了。kafka-topics.sh --describe能看分区分布和 ISRkafka-consumer-groups.sh --describe能看每个分区的 Lag 和当前消费位点kafka-dump-log.sh能直接把日志段文件里的消息打出来。这些都是排查问题时绕不开的利器尤其是在你怀疑数据本身有问题的时候。但它们的问题也很明显。第一是一次性你敲一次命令拿一次快照没有历史曲线而绝大多数 Kafka 故障的特征恰恰是“缓慢恶化”没有曲线你根本判断不了趋势。第二是没有告警能力谁也不可能每隔三十秒手动敲一遍命令。第三是有开销--describe会遍历所有消费组的所有分区组一多、分区一多这条命令本身在 broker 上就是一次不小的负担我在一个四千分区的集群上实测过全量 describe 一次要跑将近二十秒高峰期千万不要随便执行。图形化的管理工具能补上一部分比如各种开源的集群管理面板可以点开看主题详情、消费组详情日常排查很顺手。但它们的定位是“人去看”不是“机器去看”没有指标存储、没有阈值判断、没有告警通道。所以我一直把这类工具定位成“白天上班时用的放大镜”而“千里眼”必须是 7×24 小时不眨眼的那种。真正要做的是把 Kafka 内部通过 JMX 暴露出来的那几百个指标加上消费组的位点信息变成时间序列数据存起来再在上面加判断逻辑。这就是后面几节要展开的内容。1.3 为什么我把“监控”拆成“看”和“判”两件事这个拆分看起来很废话但它决定了我整套方案的架构。“看”是采集是客观事实某个时刻某个分区的 Lag 是多少、某个 broker 的 ISR 收缩了几次。采集层要做的就是忠实、完整、开销可控地把这些数字记下来它不应该包含任何业务判断。“判”是决策是主观的Lag 到了十万条算不算异常对有的业务算对日志采集类业务根本不算。把这两件事分开的好处是采集层可以做得非常薄、非常通用一套配置给全公司所有 Kafka 集群复用而判断逻辑可以按业务线各自定制改动判断规则完全不用碰采集。我见过太多团队把阈值硬编码在采集脚本里结果业务一调整脚本就得改改完还得重新发布最后没人愿意动监控就慢慢烂掉了。在这套拆分下判断层又分成两档。第一档是静态规则比如“ISR 收缩次数大于零持续三分钟就告警”简单粗暴但有效适合那些“一旦出现就一定是问题”的信号。第二档是动态评估最典型的就是消费延迟因为 Kafka 的 Lag 天然带锯齿、天然有周期性波动用固定阈值必然误报必须引入滑动窗口和速率判断。后面的第 4 节会重点讲这一档。2. 观测数据的源头指标、日志、事件怎么取舍Kafka 暴露出来的东西其实非常多光是 JMX 里的 MBean 就有上千个。全采肯定不行一是存储成本二是信噪比三是采集本身对 broker 有开销。我的一般原则是先确定故障模式再反推需要哪些指标。你能想到的每一种 Kafka 故障都应该能对应到一到三个具体指标上对应不上的说明这个指标采了也没人看。2.1 JMX 指标里真正需要长期采集的只有这几类我把常年采集的 JMX 指标压缩到六大类大概三四十个指标名其余的全部靠白名单排除。这个量级下单 broker 的指标序列数能控制在千条以内存储和抓取压力都很小。类别代表指标对应故障模式建议采集粒度副本与分区健康UnderReplicatedPartitions、OfflinePartitionsCount、PartitionCount副本落后、分区不可用、分区数异常增长每 30 秒控制器状态ActiveControllerCount脑裂、控制器频繁切换每 15 秒请求与线程池RequestHandlerAvgIdlePercent、NetworkProcessorAvgIdlePercent、TotalTimeMs的各分位请求处理不过来、慢请求每 30 秒流量与消息量MessagesInPerSec、BytesInPerSec、BytesOutPerSec流量突增突降、写入停滞每 30 秒日志与磁盘LogStartOffset、LogEndOffset、Size、LogFlushRateAndTimeMs磁盘写满、日志段异常每 60 秒延迟操作与积压PurgatorySize、各DelayedOperation计数等待确认的请求堆积每 30 秒这里有个细节值得单独说UnderReplicatedPartitions这个指标很多模板是直接取绝对值然后设阈值为 0。但实际运维中滚动重启、机房网络抖动都会让它短暂变成非零如果for设得太短一天能报几十次。我一般把for设成 3 到 5 分钟并且额外加一条“持续增长”的判断——如果它在一个小时里只出现过一次然后恢复那多半是正常波动。反过来ActiveControllerCount这个指标的特殊之处在于它应该是整个集群求和等于 1而不是每个 broker 都大于零。如果你的告警规则写成 0那当出现两个控制器的时候你反而不会告警这恰恰是最危险的脑裂场景。正确的写法是对集群做 sum 之后再判断是否等于 1。还有一类容易漏的指标是ISR 收缩和扩张的速率。IsrShrinksPerSec和IsrExpandsPerSec这两个是速率型指标正常情况下应该是零。它们的价值在于有时候副本落后会在很短时间内自我恢复你按 30 秒采样看UnderReplicatedPartitions可能永远抓不到那个尖峰但速率指标会把它记录下来。我在一次磁盘 IO 抖动的事故里就是靠这两个指标定位到问题的当时大盘上副本数一直是绿的只有 ISR 收缩速率有个小尖峰。2.2 采集频率与保留周期怎么算出来采集频率不是拍脑袋定的它直接决定了你的存储成本和故障发现延迟必须算。我一般按“故障可容忍发现延迟”来倒推假设某类故障我希望在 1 分钟内发现那么采集间隔加上规则里的for时长加起来不能超过 1 分钟。这意味着采集间隔最多 30 秒for最多 30 秒。但对 ISR 这类需要捕捉瞬时尖峰的指标30 秒明显不够得压到 15 秒甚至 10 秒。存储量我通常这么估。假设一个集群有 20 个 broker总共 4000 个分区。消费组侧的指标是分区级的每个分区大致产生 3 到 4 条序列Lag、当前位点、日志末端位点等四千个分区就是一万六千条活跃序列。按 30 秒抓一次算每分钟产生一万六千乘以二也就是三万二千个样本一天下来大约 4600 万个样本。时间序列库压缩后每个样本大概 1 到 2 字节取中间值 1.5 字节一天约 70MB。保留 30 天不到 2.5GB这个量级完全可以接受。broker 侧的 JMX 指标按白名单控制在单机一千条序列20 台就是两万条30 秒一次一天约 5760 万样本压缩后 80MB 出头。两边加起来一天 150MB 左右30 天 4.5GB。这个数字是我做容量规划时的基线如果实际用量远超它基本就说明白名单没配好把大量冷门指标也采进去了。一个很实用的省空间技巧把LogStartOffset、LogEndOffset这类只增不减的绝对值指标换成delta或者用increase()在查询时算长期保留绝对值意义不大而增量信息更有诊断价值。至于保留周期我按用途分三档。原始秒级数据保留 15 天够应付绝大多数事后复盘降采样到 5 分钟粒度的数据保留 90 天用来看容量趋势再往上的日级汇总永久保留用来做年度容量规划。这套分层策略在任何主流时序库里都能配出来成本能降一大截。2.3 日志和事件别忘了控制器和重平衡这两条线索指标是数字日志是故事。很多 Kafka 故障指标上只能看到一个“结果”真正的原因藏在日志里。我特别关注两类日志。第一类是控制器日志。控制器在切换、分区重新分配、副本状态变更的时候会打日志。如果你在指标上看到ActiveControllerCount抖了一下接着去翻控制器日志通常能找到“某个 broker 会话超时被踢出”这样的记录。这类日志的价值在于它有时间戳和具体对象能把一个抽象的指标波动还原成一个具体事件。我一般会把控制器日志按关键字比如 Controller、epoch、partition 状态变更单独做一条日志管道出问题时直接搜索比在大盘上瞎点快得多。第二类是消费组重平衡日志。重平衡是 Lag 波动的头号元凶。一次重平衡期间所有消费者暂停消费Lag 直线上升重平衡结束后如果消费能力足够Lag 会自己降回去。如果只看 Lag 曲线你只会看到一个尖峰然后恢复完全不知道发生了什么。但如果同时能看到“这个组在过去一小时重平衡了 12 次”那结论立刻就清楚了。我用过一个很笨但很有效的办法写个小脚本定期消费__consumer_offsets这个内部主题解析出消费组的成员变更和位点提交记录。虽然这个主题是二进制格式解析起来有点麻烦但它是最权威的事件来源。更省事的做法是用 Kafka 自身的消费组命令行工具定期 dump 一次状态把成员数、分区分配、当前位点存成时间序列这样 Lag 和成员数就能在同一个时间轴上对照看因果关系一目了然。3. 从零搭一条能用的监控链路前面讲的是“看什么”这一节讲“怎么搭”。我不打算给一套只有在理想环境下才能跑通的配置而是按我自己实际落地的顺序从选型到配置到告警规则一步步走。3.1 组件选型几种常见组合的取舍监控链路的骨架无非是“采集 - 存储 - 展示 - 告警”四段。Kafka 场景下采集有两路一路是 JMX 指标一路是消费组位点。这两路的数据源不一样采集方式也不一样必须分开配。JMX 那一路标准做法是挂一个 JMX 采集器把 JVM 的 MBean 翻译成 HTTP 端点上的文本格式。关键在于它支持白名单配置你可以只暴露需要的指标避免把整个 MBean 树都吐出来——那玩意儿一个大 broker 能吐几万行抓一次要好几秒对 broker 是实实在在的负担。消费组位点那一路需要一个专门的采集器去调 Kafka 的接口拿位点市面上有几个成熟的开源实现配置里指定 broker 地址和要监控的消费组正则即可。存储和展示这块时序库加可视化面板的组合已经很成熟了我不建议自研。真正需要自己花心思的是告警规则的编排以及告警怎么路由到人。我见过太多团队在选型上纠结两周结果告警规则只写了三条等于白搭。有一个取舍值得单独说要不要用那种“一站式”的 Kafka 监控平台。这类平台开箱即用装完就有一堆现成图表确实省事。但它们的判断逻辑是黑盒你想改一条阈值可能要改配置文件甚至改源码而且通常只覆盖消费组这一层集群层的细节看不到。我的选择是核心链路自己搭一站式平台只作为日常排查的辅助工具给团队里不太熟悉 Kafka 的同学用。3.2 部署与抓取配置先给一份 JMX 采集器的配置骨架重点是白名单那部分。这个文件通常挂在 broker 启动参数上通过-javaagent方式加载。# kafka-jmx.yml lowercaseOutputName: true lowercaseOutputLabelNames: true rules: # 副本与分区健康必须 - pattern: kafka.servertypeReplicaManager, name(UnderReplicatedPartitions|PartitionCount|OfflinePartitionsCount)Value name: kafka_server_replicamanager_$1 type: GAUGE # 控制器必须注意这是集群级求和指标 - pattern: kafka.controllertypeKafkaController, nameActiveControllerCountValue name: kafka_controller_activecontrollercount type: GAUGE # ISR 速率推荐用于捕捉瞬时尖峰 - pattern: kafka.servertypeReplicaManager, name(IsrShrinksPerSec|IsrExpandsPerSec)Count name: kafka_server_replicamanager_$1 type: COUNTER # 请求处理线程池空闲率 - pattern: kafka.servertypeKafkaRequestHandlerPool, nameRequestHandlerAvgIdlePercentOneMinuteRate name: kafka_server_requesthandleravgidle type: GAUGE # 主题级流量 - pattern: kafka.servertypeBrokerTopicMetrics, name(MessagesInPerSec|BytesInPerSec|BytesOutPerSec)OneMinuteRate name: kafka_server_brokertopicmetrics_$1 type: GAUGE # 其余全部丢弃 - pattern: .*这份配置的关键在最后一行把所有没显式匹配的指标全部丢弃。没有这一行你的序列数会瞬间涨十倍。另外注意OneMinuteRate这个后缀JMX 里的 Meter 类指标有 Count、OneMinuteRate 等多个属性选哪个要看用途Count 适合做increase()计算Rate 适合直接看瞬时值。我一般两个都留着因为排查时的视角不一样。采集器的抓取配置大概长这样# scrape 配置片段 scrape_configs: - job_name: kafka-broker scrape_interval: 30s static_configs: - targets: - broker-01:7071 - broker-02:7071 - broker-03:7071 - job_name: kafka-consumergroup scrape_interval: 30s static_configs: - targets: [exporter:9308]消费组采集器那边有个容易被忽略的参数要监控哪些消费组。默认是全部但生产环境里经常有一堆已经废弃的历史消费组它们的位点常年不动Lag 永远在涨然后你就收获了一堆永远消不掉的告警。我的做法是用正则白名单只监控命名规范的活跃消费组同时在清理流程里要求下线消费组时必须同步改白名单。还有一个开销问题。消费组采集器是逐组装、逐分区去查位点的组数和分区数一大单次采集就可能跑不完一个周期。我的经验是如果单次采集耗时超过抓取间隔的一半就必须考虑拆分了——比如按业务线拆成多个采集实例每个只负责一部分组。不然会出现采集队列堆积指标时间戳错乱图画出来是断断续续的。3.3 告警规则怎么写才不狼来了告警规则我总结了三条原则优先用速率而不是绝对值、一定要配for、同一个故障只保留一个入口告警。先看几条我实际在用的规则groups: - name: kafka-core rules: - alert: KafkaControllerNotUnique expr: sum by (cluster) (kafka_controller_activecontrollercount) ! 1 for: 1m labels: severity: critical annotations: summary: 集群 {{ $labels.cluster }} 控制器数量异常 - alert: KafkaUnderReplicated expr: sum by (cluster, instance) (kafka_server_replicamanager_underreplicatedpartitions) 0 for: 5m labels: severity: warning - alert: KafkaRequestHandlerBusy expr: kafka_server_requesthandleravgidle 0.2 for: 10m labels: severity: warning - alert: KafkaLagGrowingFast expr: deriv(sum by (consumergroup) (kafka_consumergroup_lag)[10m:1m]) 500 for: 10m labels: severity: warning第一条规则就是前面说的sum ! 1的写法这是集群层最重要的一条。第二条加了 5 分钟的for过滤掉滚动重启的噪音。第三条是请求处理线程池的空闲率低于 20% 持续 10 分钟说明 broker 已经在吃力了虽然还没出事但该扩容了——这类“预警型”告警比“故障型”告警更有价值。第四条用到了子查询[10m:1m]意思是把过去 10 分钟的 Lag 按 1 分钟粒度重采样之后再求导。它的含义是“每分钟 Lag 增长超过 500 条”而不关心 Lag 的绝对值。这条规则特别适合那种基础 Lag 本身就很高、但消费速度跟得上的场景——绝对值报警会一直响而增长速度报警只在真正恶化时才响。一个血的教训别给同一个故障配多条告警。有段时间我给消费组延迟配了“绝对值超标”“增长率超标”“消费组无成员”三条规则结果一次故障同时触发三条值班同学看到三条不同的告警以为是三个独立问题排查方向直接跑偏。后来的做法是只保留一条入口告警其他判断写在告警内容里作为附加信息。4. 消费延迟这只眼睛最难的一环前面三节都是铺垫这一节才是“千里眼”真正难的地方。集群层的指标都是硬事实看一眼就知道对不对而消费延迟这个数字表面上人人都懂实际上坑最多。4.1 Lag 这个数字的三个陷阱第一个陷阱Lag 会周期性锯齿。很多消费端用的是批量拉取加手动提交也就是说它攒够 500 条或者攒够 5 秒才提交一次位点。在这个间隔内Lag 会先涨上去然后一下子掉回接近零。如果你用 30 秒采样、阈值为零那这条曲线看起来就跟心电图一样天天报警天天恢复。解决办法有两个一是把判断窗口拉长到远超提交间隔二是看时间序列的平均值而不是瞬时值。第二个陷阱Lag 为零不代表没延迟。这是最容易被忽略的一点。假设消费者拉了一批消息位点已经提交了但业务逻辑在处理其中某一条的时候卡在一个数据库慢查询上卡了十分钟。这十分钟里位点是已经提交的Lag 一直是零监控一片绿但实际业务已经延迟了十分钟。Lag 只能反映“拉到了没”没法反映“处理完没”。要覆盖这个盲区就得加业务侧的埋点消息里带上生产时间戳处理完的时候算一下端到端耗时单独打一个延迟指标出来。这个指标的告警阈值可以直接按业务的 SLA 来定比 Lag 靠谱得多。我在一个订单状态同步的场景里就是这么做的Lag 那条线永远平静但端到端延迟那条线救过一次命。第三个陷阱Lag 是所有分区的和但故障往往只在个别分区。一个一千分区的主题Lag 总和十万听起来不严重平均每个分区才一百条。但如果这十万全集中在三个分区上那这三个分区的消费已经严重落后而其他分区可能完全空闲——这种情况通常意味着 key 设计有问题或者某个分区的 leader 所在 broker 有性能问题。所以看 Lag 一定要能下钻到分区级聚合值只能用来做总览。4.2 用时间维度重算延迟既然按条数算的 Lag 有这么多毛病那有没有更直观的度量有就是按时间算的延迟消费位置落后了多少秒。这个数字的业务含义非常明确——“业务看到的数据比实时晚了多久”比“积压了多少条”好理解一万倍也更容易跟业务方对齐 SLA。Kafka 没有直接提供这个指标但可以用一个讨巧的办法算出来。Kafka 的listOffsets接口支持按时间戳查位点你给它一个时间点它返回那个时间点之后第一条消息的位点。反过来如果我知道当前提交的位点我就可以用二分法在时间轴上逼近找到“这个位点对应的大致时间”延迟就是当前时间减去它。具体实现上先拿消费组的已提交位点然后在[当前时间 - 最大回溯时间, 当前时间]这个区间里二分每次取中点去查位点判断查到的位点是不是大于已提交位点据此缩小范围。回溯一小时的话二分 12 次精度就能到一秒以内完全够用。from confluent_kafka import Consumer, TopicPartition from confluent_kafka.admin import AdminClient, OffsetSpec import time def time_lag(bootstrap, group, topic, partition, max_lookback_s3600): 返回该分区消费位置落后的大致秒数-1 表示无法判断 consumer Consumer({ bootstrap.servers: bootstrap, group.id: group, enable.auto.commit: False, }) tp TopicPartition(topic, partition) committed consumer.committed([tp])[0] if committed.offset 0: consumer.close() return -1 # 组里没有该分区的位点 admin AdminClient({bootstrap.servers: bootstrap}) def offset_at(ts_ms): futures admin.list_offsets({tp: OffsetSpec.for_timestamp(ts_ms)}) return futures[tp].result().offset now_ms int(time.time() * 1000) lo now_ms - max_lookback_s * 1000 hi now_ms # 先判边界回溯窗口之前就已经落后说明延迟超过窗口上限 if offset_at(lo) committed.offset: consumer.close() return max_lookback_s while hi - lo 1000: mid (lo hi) // 2 if offset_at(mid) committed.offset: hi mid else: lo mid consumer.close() return int((now_ms - lo) / 1000)这段代码有几个地方需要说明。第一list_offsets是异步接口返回的是 future要逐个result()。第二边界判断很关键如果消费位置落后超过一小时二分区间就不够用了这时应该直接返回上限值并标记为“严重落后”而不是给一个错误的精确值。第三这个函数的单次调用会发起十来次 RPC绝对不能在主采集循环里对每个分区都跑一遍——正确做法是只对Lag 排名前 N 的分区做时间延迟换算或者按业务重要性挑几个关键分区做。我自己线上是这么落地的主链路每 30 秒采一次分区级 Lag同时每分钟对每个消费组里 Lag 最大的三个分区做时间延迟换算把结果打成一个带消费组标签的指标。这样既控制了开销又拿到了对业务方最直观的那个数字。4.3 阈值怎么定从业务容忍度倒推Lag 告警阈值最忌讳的定法是“拍个整数”比如十万。正确的定法是从业务侧往回推。举个例子。假设某个主题承载的是订单状态同步产品明确要求“状态变更后三分钟内必须能被下游看到”我们把它叫业务容忍延迟 T。这个主题的消费 TPS 稳定在 2000 条每秒那么理论上的 Lag 阈值就是 2000 × 180 36 万条。也就是说Lag 到了 36 万业务才刚刚开始不满足 SLA在这之前告警都属于过早。但这里还要留出处理时间。假设消费端扩容一次需要 10 分钟含审批、发布那么告警应该在这 10 分钟之前触发也就是 Lag 达到 2000 × (180 - 600) 显然不合理正确的做法是反过来算应该在剩余可处理时间足够时告警。设业务容忍 3 分钟扩容需要 10 分钟那实际上这个业务本身就撑不住扩容流程必须靠自动扩容或者提前预警。这种“算完发现逻辑不成立”的情况非常常见它说明真正的解决方案不是调阈值而是改架构。对于扩容流程快的场景比如自动扩缩容 1 分钟内能完成那阈值就设成TPS × (T - 处理时间)即 2000 × (180 - 60) 24 万。再配合一条增长率告警作为兜底。还有一类业务是延迟敏感但流量很小的比如权限变更通知TPS 只有几十要求 10 秒内生效。这种情况下按条数算阈值完全没意义几十条就超了。这种业务应该直接用 4.2 节的时间延迟指标阈值设 10 秒简单直接。我的建议是条数型 Lag 用于容量规划和趋势观察时间型延迟用于告警。两者别混用否则你会一直纠结阈值怎么调都不对。5. 排查实录那些指标看起来很怪的时刻搭好链路之后真正考验人的是解读。下面这些都是我在实际项目里遇到过的、指标表现和真实原因对不上的情况整理成速查表方便对照。5.1 常见问题速查表现象常见根因排查动作某个消费组 Lag 恒为 0但业务反馈延迟采集白名单里没有这个组组名拼写有误消费者已切换成新的组 ID用消费组命令行工具确认组名和位点是否在推进Lag 曲线呈规律锯齿告警反复触发批量消费加手动提交导致属正常现象拉长判断窗口改用 5 分钟均值或增长率判断Lag 突然飙升后自行恢复消费组发生重平衡其间全部消费者暂停对照消费组成员数曲线确认重平衡时间点个别分区 Lag 长期独大key 设计倾斜或该分区 leader 位于性能较差的节点看分区级 Lag 分布检查 key 分布是否均匀位点在推进但端到端延迟持续升高消费端处理逻辑变慢比如下游接口超时打得点指标按消息维度看耗时分布消费组成员数频繁上下跳动心跳超时设置过短或消费者 GC 停顿过长查消费者侧 GC 日志和心跳配置采集器采集超时、数据断点消费组数量过多单次采集跑不完一个周期拆分采集实例或缩短采集范围磁盘使用率突然加速增长某主题流量突增或消息体大小翻倍看主题级字节流量和平均消息大小这张表我自己打印出来贴在工位旁边过。它最大的价值是让排查有个起点不至于盯着大盘发愣。5.2 几个我踩过的坑坑一只看聚合不看分区把倾斜当成正常。有一回某个主题的 Lag 稳定在八万左右浮动一直没超阈值大家也就习惯了。直到业务方说某个类目的商品库存更新老是慢半拍我们才去下钻分区发现八万里有七万都压在两个分区上而这两个分区对应的正好是那几个大类目。根本原因是 key 用了类目 ID大类目的消息量是小类目的上百倍。最后的解决方案是给 key 加了一层随机后缀打散把一个热点拆成多个分区消费端再按类目做聚合。这个教训是聚合指标永远只能用来发现分区指标才能用来定位。坑二JMX 端口开得太随意。早期为了图省事直接把 JMX 端口开在 broker 所在机器的所有网卡上采集器从外部直连。后来做安全扫描的时候被提了个高危——这个端口是无认证的任何人都能连上去读到全部 MBean甚至能触发一些管理操作。后来改成只监听本机回环采集器同机部署外部通过网关转发。这件事提醒我观测系统本身也是攻击面配置的时候必须按最小暴露原则来。坑三以为指标缺失是采集器坏了。有一次某台 broker 的指标全部消失了第一反应是采集器挂了重启了一遍还是没数据。折腾半小时才发现是那台机器的磁盘写满broker 自己把日志写到了别处JMX 端口根本没起来。指标消失本身就是一种告警信号后来我加了一条规则任何 broker 的指标中断超过两分钟就告警。这条规则后来救过两次。坑四把重平衡次数当成越低越好。有段时间我一直在优化想把某消费组的重平衡次数压到零。后来发现这个组是因为业务方会不定时上线新实例每次上线都会触发一次重平衡这属于正常发布流程。真正该优化的是重平衡的影响时长——用增量协作式的分配策略让重平衡期间大部分分区继续消费而不是全部停下来。这个改动比追求零重平衡有价值得多。5.3 上线前的一份自查清单最后给一份我每次新集群上线都会过一遍的清单一共十条都是被坑出来的。[ ] 集群层四件套控制器唯一性、离线分区数、未充分复制分区数、ISR 收缩速率全部有采集有告警[ ] 采集白名单生效单 broker 序列数在千条以内实际抓取耗时小于抓取间隔的一半[ ] JMX 采集端口只监听内网或本机不对外暴露采集账号只读[ ] 消费组白名单配置正确已废弃的组全部排除避免僵尸告警[ ] Lag 告警至少配了绝对值加增长率两条且为同一条告警的不同触发条件不是两条独立告警[ ] 至少有一个核心业务配了端到端延迟埋点能覆盖“位点已提交但处理卡住”的场景[ ] 关键主题分区级 Lag 可下钻能快速识别倾斜[ ] 指标中断本身有告警任何 broker 失联两分钟内必有人知道[ ] 告警路由分级明确集群级电话、分区级工单、消费组级值班群[ ] 大盘上每个图旁边都写了“这个图异常时该看哪个指标”避免交接时无人能读懂这套东西搭完之后我最直观的感受是值班的心态变了。以前是业务打电话过来然后一群人慌慌张张跑命令现在是告警先响值班同学点开大盘看一眼就知道是哪个环节、影响多大、该找谁。这只“千里眼”真正的价值不在于多画了几个图而在于它把“发现问题的时刻”往前挪了而挪出来的这段时间往往就是故障和事故之间的那道分界线。我个人在实际操作中的体会是监控这件事最难的从来不是技术选型和配置而是决定哪些异常值得叫醒一个人。规则写多了没人看写少了漏事故。我的做法是每季度把过去三个月的告警全部翻一遍统计每条规则的触发次数和真实故障占比占比低于一成的直接删掉或者降级成工单。删规则比加规则更难但只有敢删这套东西才能长期活着。