外观
监控与容量:十个指标看住集群
Kafka 的 JMX 指标有几百个。真正需要配告警的只有十个,其余用于定位。
场景
集群"感觉慢"时,先区分是写不进、读不出、还是副本不同步。这三类对应完全不同的指标。
一、打开 JMX
broker 默认不开 JMX。用环境变量开:
ini
# /etc/systemd/system/kafka.service 的 [Service] 段
Environment="KAFKA_JMX_OPTS=-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.rmi.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Djava.rmi.server.hostname=10.0.0.31"bash
systemctl daemon-reload && systemctl restart kafka
ss -lntp | grep 9999text
LISTEN 0 50 0.0.0.0:9999 0.0.0.0:* users:(("java",pid=28441,fd=112))9999 端口绝对不能开公网
authenticate=false 时任何能连上的人都能读所有 JMX 指标,也能调用 MBean 上的写操作(比如改配置)。只允许内网 Prometheus 访问。
-Djava.rmi.server.hostname 必须写真实 IP。不写的话 RMI 会返回主机名,客户端解析不了就是 Connection refused。
二、十个必看指标
| # | 指标 | MBean | 正常值 |
|---|---|---|---|
| 1 | 未同步分区数 | kafka.server:type=ReplicaManager,name=UnderReplicatedPartitions | 0 |
| 2 | 离线分区数 | kafka.controller:type=KafkaController,name=OfflinePartitionsCount | 0 |
| 3 | 活跃 controller 数 | kafka.controller:type=KafkaController,name=ActiveControllerCount | 每台 0 或 1,全集群和为 1 |
| 4 | 每秒入流量 | kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec | 对比基线 |
| 5 | 每秒出流量 | kafka.server:type=BrokerTopicMetrics,name=BytesOutPerSec | 对比基线 |
| 6 | 每秒消息数 | kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec | 对比基线 |
| 7 | 请求处理线程空闲率 | kafka.server:type=KafkaRequestHandlerPool,name=RequestHandlerAvgIdlePercent | > 0.3 |
| 8 | 网络线程空闲率 | kafka.network:type=SocketServer,name=NetworkProcessorAvgIdlePercent | > 0.3 |
| 9 | 日志刷盘耗时 | kafka.log:type=LogFlushStats,name=LogFlushRateAndTimeMs | 99th < 10ms |
| 10 | 消费积压 | 外部采集(见第六节) | 不持续增长 |
1. UnderReplicatedPartitions
bash
# 用 kafka-exporter 或 JMX 客户端取值,命令行快速版:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --under-replicated-partitions | wc -ltext
0持续 > 0 说明有副本跟不上。它不区分"慢"和"死",需要再看 IsrShrinksPerSec:
- ISR 收缩但很快恢复 → 网络抖动/GC 停顿,可容忍。
- ISR 持续收缩不恢复 → 副本 broker 有问题。
2. OfflinePartitionsCount
0 就是生产事故:这些分区的 leader 全挂了,不可读不可写。
bash
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --unavailable-partitions3. ActiveControllerCount
每台 broker 的这个值要么 0 要么 1,全集群加起来必须恰好是 1。
- 全 0:没有 controller,元数据变更全部停摆(分区 leader 选举、topic 创建)。
- 出现两个 1:脑裂。极罕见,通常是 ZK 会话或 KRaft quorum 出问题。
4~6. 流量三兄弟
text
BytesInPerSec 生产者写入(含副本复制)
BytesOutPerSec 消费者读取 + 副本拉取
MessagesInPerSec 消息条数(不含副本复制)BytesInPerSec 突然掉 0 而业务还在跑,说明生产者连不上。BytesOutPerSec 远大于 BytesInPerSec,通常是消费者在追历史数据。
7~8. 线程池空闲率
text
RequestHandlerAvgIdlePercent < 0.3 → 请求处理不过来,要加 num.io.threads
NetworkProcessorAvgIdlePercent < 0.3 → 网络线程饱和,要加 num.network.threads这两个值降到 0 就是请求排队,客户端表现为超时。加线程能缓解,但根因往往是磁盘慢或分区过多。
9. LogFlushRateAndTimeMs
Kafka 靠操作系统页缓存,log.flush.interval.messages 默认极大(几乎不主动 flush),所以这个值平时很小。它变大说明:
- 磁盘 IO 饱和。
log.flush.interval.messages被调小了(不建议,flush 交给 OS 更好)。
10. 消费积压
严格说这不是 broker 指标,是消费组指标。只能外部采集。
三、定位用的二级指标
| 现象 | 指标 |
|---|---|
| leader 频繁切换 | kafka.controller:type=ControllerStats,name=LeaderElectionRateAndTimeMs |
| 选了落后副本当 leader | kafka.controller:type=ControllerStats,name=UncleanLeaderElectionsPerSec |
| 分区数增长 | kafka.server:type=ReplicaManager,name=PartitionCount |
| 副本拉取慢 | kafka.server:type=ReplicaFetcherManager,name=MaxLag,clientId=Replica |
| 生产者被限流 | kafka.server:type=BrokerTopicMetrics,name=ProduceMessageConversionsPerSec |
| 请求总数 | kafka.network:type=RequestMetrics,name=RequestsPerSec,request=Produce |
| 请求排队时间 | kafka.network:type=RequestMetrics,name=RequestQueueTimeMs,request=Produce |
| 磁盘占用 | 见 Topic 与分区 的 kafka-log-dirs.sh |
UncleanLeaderElectionsPerSec > 0 是丢数据的确认信号。配合 unclean.leader.election.enable=false 后它应该永远是 0。
四、用 kafka-exporter 采集
bash
docker run -d --name kafka-exporter -p 9308:9308 \
danielqsj/kafka-exporter:latest \
--kafka.server=10.0.0.31:9092 --kafka.server=10.0.0.32:9092 --kafka.server=10.0.0.33:9092bash
curl -s localhost:9308/metrics | grep -E 'kafka_brokers|kafka_topic_partitions|kafka_consumergroup_lag' | headtext
# HELP kafka_brokers Number of Brokers in the Kafka Cluster.
# TYPE kafka_brokers gauge
kafka_brokers 3
# HELP kafka_topic_partitions Number of partitions for this Topic
# TYPE kafka_topic_partitions gauge
kafka_topic_partitions{topic="order"} 6
# HELP kafka_consumergroup_lag Current Approximate Lag per consumergroup and topic
# TYPE kafka_consumergroup_lag gauge
kafka_consumergroup_lag{consumergroup="order-group",topic="order"} 202Prometheus 采集配置:
yaml
scrape_configs:
- job_name: kafka
static_configs:
- targets: ['10.0.0.31:9308']
scrape_interval: 30s告警规则(Prometheus 语法):
yaml
groups:
- name: kafka
rules:
- alert: KafkaUnderReplicated
expr: sum(kafka_topic_partition_under_replicated_partition) > 0
for: 5m
labels: { severity: warning }
annotations: { summary: "存在未同步分区 {{ $value }} 个" }
- alert: KafkaConsumerLagHigh
expr: sum by (consumergroup) (kafka_consumergroup_lag) > 100000
for: 10m
labels: { severity: critical }
annotations: { summary: "消费组 {{ $labels.consumergroup }} 积压 {{ $value }}" }
- alert: KafkaBrokerDown
expr: kafka_brokers < 3
for: 2m
labels: { severity: critical }五、不装 exporter 也能巡检
bash
B=10.0.0.31:9092
K=/opt/kafka/bin
echo "brokers: $($K/kafka-broker-api-versions.sh --bootstrap-server $B | grep -c 'id:')"
echo "under-replicated: $($K/kafka-topics.sh --bootstrap-server $B --describe --under-replicated-partitions | grep -c Partition)"
echo "unavailable: $($K/kafka-topics.sh --bootstrap-server $B --describe --unavailable-partitions | grep -c Partition)"
$K/kafka-consumer-groups.sh --bootstrap-server $B --all-groups --describe 2>/dev/null \
| awk 'NR>1 && $6 ~ /^[0-9]+$/ {s[$1]+=$6} END {for (g in s) printf "lag %-24s %d\n", g, s[g]}' | sort -k3 -rntext
brokers: 3
under-replicated: 0
unavailable: 0
lag order-group 202
lag report-group 0写进 cron,每 5 分钟跑一次,输出变化就告警。这比装一整套 Prometheus 快得多。
六、消费 lag 的正确算法
text
LAG = LOG-END-OFFSET - CURRENT-OFFSET但注意两点:
read_committed下LOG-END-OFFSET用的是 LSO(最后一个稳定 offset),未提交事务的消息不算在内,所以 lag 会显得偏小。- 如果消费者不活跃(
CONSUMER-ID全-),CURRENT-OFFSET是上次提交的值,lag 可能已经很大但没人处理。
按时间估算处理进度:
bash
# 消费到 1761549000000 这个时间点需要多少 offset
/opt/kafka/bin/kafka-get-offsets.sh --bootstrap-server $B --topic order --time 1761549000000七、容量评估
磁盘
text
所需磁盘 = 日增原始量 × 副本数 × 保留天数 ÷ 压缩比 × 1.3实例:日增 80GB、RF=3、保留 7 天、zstd 压缩比 3.5、预留 30%:
text
80 × 3 × 7 ÷ 3.5 × 1.3 ≈ 624 GB单块盘建议不超过 4TB(大了故障重建太慢),3 台每台挂 2 块 1TB 做 JBoss/RAID0 更现实。
网络
text
入方向 = 生产速率
出方向 = 副本复制速率 (RF-1) × 生产速率 + 消费速率RF=3、生产 100MB/s、消费 80MB/s:
text
入 ≈ 100 MB/s ≈ 800 Mbps
出 ≈ 2×100 + 80 = 280 MB/s ≈ 2240 Mbps出方向是瓶颈。千兆网卡(125MB/s)根本扛不住这种量,需要万兆。
分区数
| 约束 | 上限 |
|---|---|
| 单 broker 分区数 | 2000~4000(经验值) |
| 单集群分区数 | 20 万(ZK 模式瓶颈)、百万(KRaft) |
| 每次 rebalance 的分区数 | 几千以上就会明显变慢 |
八、告警阈值
| 指标 | 警告 | 严重 |
|---|---|---|
UnderReplicatedPartitions | > 0 持续 5 分钟 | > 0 持续 30 分钟 |
OfflinePartitionsCount | > 0 | 立即 |
ActiveControllerCount 总和 | ≠ 1 | 立即 |
RequestHandlerAvgIdlePercent | < 0.3 | < 0.1 |
| 磁盘使用率 | > 70% | > 85% |
| 消费 lag | > 10 万且增长 | > 100 万 |
UncleanLeaderElectionsPerSec | > 0 | 立即(已丢数据) |
| broker 数量 | 少于预期 | 少于 quorum 需求 |
九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| JMX 连不上 | java.rmi.server.hostname 没设 | 填真实 IP |
| 指标名字找不到 | 版本差异(3.x 有部分改名) | 用 jconsole 或 jcmd 浏览 MBean 树确认 |
| exporter 只连一个 broker | 只填了一个 --kafka.server | 每台都填 |
| exporter 采集很慢 | 集群 topic 太多 | 加 --topic.filter 或调大 --kafka.version |
| lag 一直是 0 | 消费组是 read_committed 且事务未提交 | 看 LSO |
| 告警风暴 | 阈值太敏感(ISR 抖动就报) | 加 for: 持续时间 |
| 磁盘快满但 retention 很短 | segment 太大,删除粒度粗 | 调小 log.segment.bytes |