跳到主要内容

Kafka 运维与监控

监控要看三层​

Kafka 出问题,症状往往在最外层(订单延迟、报表没数据),根因却可能在三层里的任意一层。所以监控也按三层铺:

  • 集群元数据层:副本同步状态、控制器状态、分区可用性。这一层出问题直接影响可用性,优先级最高。
  • 节点资源层:磁盘、网络、CPU、文件句柄。Kafka 的瓶颈 90% 在这。
  • 业务消费层:消费组 lag、消费速率。这一层是「业务到底有没有受影响」的直接体现。

三层都要有,只盯 CPU 和内存的监控等于没监控。

关键指标​

指标通过 JMX 暴露,下面这些是最该配告警的一批。kafka.server 开头的来自 Broker,kafka.controller 开头的来自 Controller。

指标JMX 名称含义告警条件
UnderReplicatedPartitionskafka.server:type=ReplicaManager,name=UnderReplicatedPartitions副本数不足的分区数量大于 0 持续 5 分钟
OfflinePartitionsCountkafka.controller:type=KafkaController,name=OfflinePartitionsCount没有 Leader 的分区数大于 0 立即告警
IsrShrinksPerSeckafka.server:type=ReplicaManager,name=IsrShrinksPerSec每秒 ISR 收缩次数持续大于 0
IsrExpandsPerSeckafka.server:type=ReplicaManager,name=IsrExpandsPerSec每秒 ISR 恢复次数只有收缩没有扩张时重点看
ActiveControllerCountkafka.controller:type=KafkaController,name=ActiveControllerCount当前节点是否为活跃控制器集群总和必须等于 1
UncleanLeaderElectionsPerSeckafka.controller:type=KafkaController,name=UncleanLeaderElectionsPerSec非 ISR 副本当选次数大于 0 立即告警
LeaderCountkafka.server:type=ReplicaManager,name=LeaderCount本节点承载的 Leader 分区数节点间偏差超过 20%
RequestHandlerAvgIdlePercentkafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercentIO 线程空闲率低于 0.3
NetworkProcessorAvgIdlePercentkafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent网络线程空闲率低于 0.3
TotalTimeMskafka.network:type=RequestMetrics,name=TotalTimeMs,request=Produce请求处理耗时(含 P99)P99 超过 100ms
MessagesInPerSeckafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec每秒入站消息数突降为 0 或突增数倍
BytesInPerSeckafka.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 的绝对值要和分区数一起看

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 会带来三个后果:

  1. 空 key 的消息不参与压缩,永远不会被清理,堆积依旧。
  2. 历史消息被重写,下游按 offset 重放时读到的内容变了——本来有 10 条变更,压缩后只剩 1 条,重放逻辑直接错乱。
  3. 额外的磁盘 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,会重写历史、打乱重放语义。
  • 迁移、重选举、重置位点这三个操作都有副作用,执行前想清楚限流和停机窗口。

日常监控能发现大部分问题,但真出故障时还是得顺着现象往下挖。下一篇《故障排查与最佳实践》按四类高频故障给出排查路径。