Kafka 运维与监控
监控要看三层
Kafka 出问题,症状往往在最外层(订单延迟、报表没数据),根因却可能在三层里的任意一层。所以监控也按三层铺:
- 集群元数据层:副本同步状态、控制器状态、分区可用性。这一层出问题直接影响可用性,优先级最高。
- 节点资源层:磁盘、网络、CPU、文件句柄。Kafka 的瓶颈 90% 在这。
- 业务消费层:消费组 lag、消费速率。这一层是「业务到底有没有受影响」的直接体现。
三层都要有,只盯 CPU 和内存的监控等于没监控。
关键指标
指标通过 JMX 暴露,下面这些是最该配告警的一批。kafka.server 开头的来自 Broker,kafka.controller 开头的来自 Controller。
| 指标 | JMX 名称 | 含义 | 告警条件 |
|---|---|---|---|
| UnderReplicatedPartitions | kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions | 副本数不足的分区数量 | 大于 0 持续 5 分钟 |
| OfflinePartitionsCount | kafka.controller:type=KafkaController,name=OfflinePartitionsCount | 没有 Leader 的分区数 | 大于 0 立即告警 |
| IsrShrinksPerSec | kafka.server:type=ReplicaManager,name=IsrShrinksPerSec | 每秒 ISR 收缩次数 | 持续大于 0 |
| IsrExpandsPerSec | kafka.server:type=ReplicaManager,name=IsrExpandsPerSec | 每秒 ISR 恢复次数 | 只有收缩没有扩张时重点看 |
| ActiveControllerCount | kafka.controller:type=KafkaController,name=ActiveControllerCount | 当前节点是否为活跃控制器 | 集群总和必须等于 1 |
| UncleanLeaderElectionsPerSec | kafka.controller:type=KafkaController,name=UncleanLeaderElectionsPerSec | 非 ISR 副本当选次数 | 大于 0 立即告警 |
| LeaderCount | kafka.server:type=ReplicaManager,name=LeaderCount | 本节点承载的 Leader 分区数 | 节点间偏差超过 20% |
| RequestHandlerAvgIdlePercent | kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent | IO 线程空闲率 | 低于 0.3 |
| NetworkProcessorAvgIdlePercent | kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent | 网络线程空闲率 | 低于 0.3 |
| TotalTimeMs | kafka.network:type=RequestMetrics,name=TotalTimeMs,request=Produce | 请求处理耗时(含 P99) | P99 超过 100ms |
| MessagesInPerSec | kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec | 每秒入站消息数 | 突降为 0 或突增数倍 |
| BytesInPerSec | kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec | 每秒入站字节数 | 结合磁盘水位看 |
几个指标背后的判断逻辑值得展开:
- UnderReplicatedPartitions 是副本健康的唯一总指标。它大于 0 意味着「某些分区的备份数不够」,可能已经在丢数据的边缘。但要注意它只反映「当前是否落后」,不区分原因——磁盘慢、网络抖动、Broker 过载都会推高它,得配合节点指标定位。
- ISR 收缩要和扩张一起看。偶尔收缩一次然后恢复是正常抖动;只有收缩没有扩张,说明那台 Follower 一直追不上,多半是要换盘或者限流了。
- ActiveControllerCount 的和必须恒等于 1。为 0 表示集群没有控制器(元数据不可写),大于 1 表示脑裂(极危险)。KRaft 下这个指标在 Broker 上恒为 0,要在 Controller 节点上采集。
- 请求延迟看 P99 而不是平均。平均值被大量快请求稀释,看不出长尾。Kafka 的
TotalTimeMs本身是按分位数暴露的,取99thPercentile。
节点资源层的指标直接用 Node Exporter 就行,重点四个:
磁盘使用率 node_filesystem_avail_bytes 单盘超过 80% 预警
磁盘写延迟 node_disk_write_time_seconds 单次写超过 10ms 要查
网络重传 node_netstat_Tcp_RetransSegs 持续增长说明网络有问题
文件句柄 process_open_fds 接近上限就直接崩
Kafka 的日志是「先涨后删」的锯齿形曲线,看到 70% 不代表危险,看到 95% 也不代表马上满——但如果保留时间还没到、写入又突然翻倍,可能几小时就满。所以更该看的是写入速率 × 剩余空间 = 还能撑多久这个推导值。
指标怎么采
Kafka 原生只输出 JMX,Prometheus 读不了 JMX,中间需要一个转换层。两种做法:
方案一:JMX Exporter 作为 javaagent 挂在 Kafka 进程上。
# 把 jmx_prometheus_javaagent 放到 /opt/kafka/libs,启动参数加上
export KAFKA_OPTS="-javaagent:/opt/kafka/libs/jmx_prometheus_javaagent-0.20.0.jar=7071:/opt/kafka/config/jmx-exporter.yml"
7071 端口就会暴露 /metrics,规则文件决定哪些 MBean 被转成指标。优点是零额外进程、指标最全;缺点是升级 Kafka 时要重新挂 agent,而且 agent 出问题会影响 Broker 本身。
方案二:独立进程拉取。 社区常用的 kafka_exporter 用 Go 写,直接连 Broker 的 JMX 或者走协议拿指标,部署简单、和 Kafka 解耦。日常监控够用,深度排障时指标粒度不如方案一。
无论哪种,最终都是让 Prometheus 抓一个 /metrics 端点——指标的存储、查询、告警这一整套是 Prometheus 负责,Kafka 只负责暴露。如果对 Prometheus 的指标类型、抓取模型还不熟,先看 核心概念与架构。
lag 监控
lag 是消费侧最重要的指标,没有之一。它等于「生产到哪」减「消费到哪」,持续增长就意味着业务在堆积。
手工查,前面用过:
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 \
--describe --group order-service
缺点是只能看当前时刻,没法画曲线,也没法告警。所以需要专门导出 lag 的工具,目前最省事的是 kafka-lag-exporter(原名 kafka-lag-monitor,Lightbend 维护)。它订阅消费组位点,把每个组的 lag 暴露成 Prometheus 指标。
配置是一个 HOCON 文件:
kafka-lag-exporter {
port = 8000
poll-interval = 30s
clusters = [
{
name = "prod"
bootstrap-brokers = "kafka-1:9092,kafka-2:9092,kafka-3:9092"
}
]
}
启动后 Prometheus 加一个 job:
scrape_configs:
- job_name: "kafka-lag-exporter"
static_configs:
- targets: ["kafka-lag-exporter:8000"]
核心指标有三个,注意都是 Gauge 语义:
| 指标 | 含义 |
|---|---|
kafka_consumergroup_group_topic_sum_lag | 某个组在某个 Topic 上的 lag 总和 |
kafka_consumergroup_group_lag | 某个组在某个分区上的 lag |
kafka_consumergroup_group_lag_max | 单分区 lag 最大值,看倾斜用 |
告警规则示例(和 Prometheus 的规则语法一致):
groups:
- name: kafka-lag
rules:
- alert: KafkaConsumerLagHigh
expr: sum by (cluster, group, topic) (kafka_consumergroup_group_topic_sum_lag) > 100000
for: 10m
labels:
severity: warning
annotations:
summary: "消费组堆积超过 10 万条"
- alert: KafkaConsumerLagGrowing
expr: deriv(kafka_consumergroup_group_topic_sum_lag[10m]) > 10
for: 15m
labels:
severity: critical
annotations:
summary: "lag 持续增长,消费能力跟不上生产"
两条规则的分工很关键:绝对值告警负责「已经堆了」,导数告警负责「正在堆」。后者往往更早发现慢性问题——比如 lag 从 100 缓慢涨到 5000,绝对阈值没触发,但 deriv 早就报警了。
lag 10 万条在 3 个分区和 300 个分区里完全是两回事。更稳的做法是再算一个「消费延迟时间」:lag 除以消费速率,得到预计追平需要多久。追平时间超过 30 分钟,才值得半夜起床。
日志段清理与 compaction
Kafka 的清理策略由 Topic 参数 cleanup.policy 控制,两种模式差别很大。
delete:按时间或大小删整段
默认策略,适合绝大多数业务消息:
cleanup.policy=delete
log.retention.hours=168
log.retention.bytes=107374182400
log.segment.bytes=1073741824
删的单位是整个日志段,不是单条消息。所以一个段即使整体过期,也要等下一个段成为活跃段之后才能删——log.segment.bytes 越大,实际保留时间越容易超出配置。
compact:按 key 保留最新值
适合「状态型」数据,比如用户配置、商品库存快照、CDC 同步的变更日志。原理是后台线程不断重写段,同一个 key 只留最新 value:
cleanup.policy=compact
# 脏数据比例达到多少触发压缩,默认 0.5
min.cleanable.dirty.ratio=0.5
# 墓碑标记(value 为 null 的记录)保留多久,给下游留够读取时间
delete.retention.ms=86400000
# 新写入的数据至少等多久才参与压缩,避免频繁重写
min.compaction.lag.ms=0
# 压缩必须在上限时间内完成,否则会告警
max.compaction.lag.ms=86400000
也可以组合成 compact,delete,既按 key 去重、又按时间清理,适合「有明确生命周期」的状态表。
日志型 Topic 慎开 compact
这是线上踩过的坑。日志类 Topic(埋点、binlog 流水、事件流)有两个特征:key 可能为空、同一个 key 会反复出现。开 compact 会带来三个后果:
- 空 key 的消息不参与压缩,永远不会被清理,堆积依旧。
- 历史消息被重写,下游按 offset 重放时读到的内容变了——本来有 10 条变更,压缩后只剩 1 条,重放逻辑直接错乱。
- 额外的磁盘 IO。压缩会重写整个段,在大流量 Topic 上产生的写放大非常可观,严重时会拖慢正常写入。
判断标准很简单:下游是按「变更历史」消费,还是按「当前状态」消费。前者用 delete,后者才用 compact。
cleanup.policy 可以热改,但改成 compact 之后立即开始重写历史段,一定要挑低峰期:
bin/kafka-configs.sh --bootstrap-server localhost:9092 \
--alter --entity-type topics --entity-name user-profile \
--add-config cleanup.policy=compact
常用运维命令速览
| 命令 | 用途 | 关键参数 |
|---|---|---|
kafka-log-dirs.sh --describe | 看每个 Broker 各目录的占用和分区分布 | --bootstrap-server、--topic-list |
kafka-reassign-partitions.sh --generate | 生成分区迁移方案 | --topics-to-move-json-file |
kafka-reassign-partitions.sh --execute | 执行迁移 / 改副本因子 | --reassignment-json-file、--throttle |
kafka-reassign-partitions.sh --verify | 确认迁移是否完成 | --reassignment-json-file |
kafka-leader-election.sh | 触发 Leader 选举,恢复负载均衡 | --election-type preferred、--all-topic-partitions |
kafka-consumer-groups.sh --reset-offsets | 重置消费位点 | --to-earliest、--to-datetime、--execute |
kafka-delete-records.sh | 按 offset 删除消息 | --offset-json-file |
kafka-dump-log.sh | 直接解析日志段文件 | --files、--print-data-log |
kafka-get-offsets.sh | 查询某时刻的 offset | --topic、--time |
几条实战经验:
- 迁移一定要限流。
--throttle 104857600(100MB/s)是起步值,不限流会把带宽占满,直接影响业务读写。迁移完记得用--verify确认,再用kafka-configs.sh把限流清掉,否则限流会一直生效。 - Leader 不均衡用 preferred election。Broker 重启后 Leader 会自动漂到活着的节点上,长期不处理就会出现「一台机器扛了 80% 的 Leader」。
--election-type preferred把 Leader 选回「首选副本」,即副本列表里的第一个。 - 重置位点必须先停消费者。组里还有活跃成员时执行
--reset-offsets会被拒绝,这不是 bug,是防止在位点跳变时出现重复处理和漏处理混在一起。 kafka-dump-log.sh是最后的手段。消息确实读不出来、又必须确认内容时,直接解析.log文件,能看到 offset、时间戳、key、value 的原始记录。
小结
- 监控按元数据层、资源层、消费层三层铺,只盯 CPU 内存等于没监控。
- 第一优先级指标:UnderReplicatedPartitions、OfflinePartitionsCount、ActiveControllerCount、ISR 收缩、P99 延迟。
- lag 要配两条告警:绝对值(已经堆了)和导数(正在堆),并换算成「追平需要多久」再判断严重程度。
- JMX 靠 agent 或 kafka_exporter 转成
/metrics,lag 靠 kafka-lag-exporter;存储、查询、告警交给 Prometheus。 - 清理策略只有两种:delete 删历史、compact 留最新;日志型 Topic 别开 compact,会重写历史、打乱重放语义。
- 迁移、重选举、重置位点这三个操作都有副作用,执行前想清楚限流和停机窗口。
日常监控能发现大部分问题,但真出故障时还是得顺着现象往下挖。下一篇《故障排查与最佳实践》按四类高频故障给出排查路径。