在上一篇文章中,我谈到了 Kafka broker 指标,以及为什么采集这些指标比它本应有的难度要大得多。
简而言之:Kafka 并没有统一的指标系统。
它既有较新的、通过 metric.reporters 配置的 Kafka Metrics SPI,也有较旧的、通过 kafka.metrics.reporters 配置的 Yammer/Coda Hale 注册表。一些重要的 broker 指标存在于其中一个系统里,另一些则存在于另一个系统里。而大多数真实的监控方案,至今仍然要经过 JMX、正则表达式、Prometheus 映射,以及那些未必与导出名称相匹配的仪表盘。
这正是创建 monedula-metrics-reporter 的原因:它是一个 Kafka 插件,能够直接通过 OTLP 将指标导出到 OpenTelemetry Collector,并用一套配置同时处理两个 Kafka 指标注册表。
但仍然缺了一块。
Broker 指标能告诉我们很多关于集群的信息,却无法完整解释客户端应用内部正在发生什么。
Broker 可以告诉我们 Produce 请求变慢了,可以告诉我们 Fetch 请求变慢了,可以告诉我们请求队列、网络处理器、副本不足的分区、失败的请求以及许多其他情况。
但有时真正的问题不仅仅是:
Broker 上正在发生什么?
而还包括:
生产者或消费者内部正在发生什么?
生产者的批处理是否高效? 它是否被限流了? 消费者是否在定期 poll? 它是否把时间花在了 rebalance 上? 某个应用的行为是否与另一个不同?
传统上,回答这些问题意味着要对每个客户端应用单独进行埋点。在 Java 中,这往往又意味着回到 JMX。而在其他语言中,则取决于客户端库、框架,以及团队搭建的可观测性方案。
在较大的组织里,这会变成一个非常熟悉的难题:Kafka 团队负责集群,但客户端应用却分属许多不同的团队。因此一旦出现问题,集群运维人员往往拿不到他们所需的客户端指标,而要拿到这些指标就需要跨团队协调、修改代码、重新部署,或者至少要重启服务。
这正是 KIP-714 试图解决的问题。
KIP-714 背后的思路
KIP-714 增加了一种由 broker 驱动的方式来采集 Kafka 客户端的指标。
不再需要每个应用团队各自配置自己的指标导出器,Kafka 集群可以定义客户端指标订阅。一份订阅规定了应采集哪些客户端指标,以及客户端应以多高的频率推送这些指标。
随后,客户端使用 Kafka 协议将自己的指标推送到 broker。负载本身采用 OTLP 格式,因此 broker 端的插件可以将其转发到 OpenTelemetry Collector、时序数据库或任何其他可观测性后端。
关键在于控制权的方向。
由集群运维人员决定采集什么。
支持 KIP-714 的客户端可以接收订阅并推送被请求的指标。应用无需暴露 JMX,无需 sidecar 导出器,也无需自定义的 Prometheus 端点。
但仍有两个前提条件:
- Broker 必须配置了客户端遥测插件。
- 至少存在一份客户端指标订阅。
如果没有订阅,客户端即使支持该协议,也不会推送指标。
这是一个很好的设计。客户端指标不会被意外采集,运维人员必须在集群端显式地启用采集。
一个重要的细节:并非每个客户端都发送相同的指标
KIP-714 创建了一套采集客户端指标的通用机制,但它并不会神奇地让每个 Kafka 客户端都暴露相同的指标集合。
你能得到哪些指标,取决于客户端库。
目前 Java 客户端发送的指标集合最为丰富。这并不意外:Java 客户端是参考实现,它拥有一个非常成熟的内部指标注册表,而且许多 Kafka 指标最初就是在这里设计的。
其他客户端可能只暴露较小的集合。基于 librdkafka 的客户端(例如 Python、Go、.NET 和 Node 封装所使用的客户端)同样可以参与 KIP-714,但它们提供的指标未必与 Java 客户端发送的完全一致。
因此,正确的心智模型应该是:
KIP-714 标准化的是客户端指标被请求和传输的方式。 实际的指标集合仍然取决于客户端的实现。
这在构建仪表盘时很重要。
一些面板可以基于通用指标构建,应当能在不同客户端库之间通用。另一些面板则只有在客户端确实发送了更丰富的指标集合时才有意义。实际上,如今 Java 客户端提供的可观测性最为完整。
为什么要把这一功能加入 monedula-metrics-reporter?
起初,monedula-metrics-reporter 只关注 broker 指标。
它处理 Kafka SPI 注册表。 它处理 Yammer 注册表。 它将两者都通过 OTLP 导出。 它以资源属性的形式加入了 broker 身份信息。 它附带了与所发出指标相匹配的 Grafana 仪表盘。
但一旦有了 KIP-714,一个非常自然的下一步便浮现出来:
既然插件已经运行在 broker 内部,并且已经将 OTLP 导出到 collector,那么它也应该处理客户端推送的指标。
否则,Kafka 监控仍然是割裂的。
你会有一条路径用于 broker SPI 指标,另一条路径用于 Yammer 指标,还要再来一个插件或组件用于 KIP-714 客户端遥测。
这样虽然行得通,但会违背简化 Kafka 指标栈的初衷。
因此,新版本的 monedula-metrics-reporter 现在能够处理三种指标来源:
- Kafka Metrics SPI
- Yammer / Coda Hale broker 指标
- KIP-714 客户端推送的遥测数据
目标很简单:一个插件、一条 collector 流水线、一套仪表盘。
客户端指标会经由与 broker 指标相同的 OpenTelemetry 路径进行转发。它们可以携带相同的 broker 标签,例如 kafka_cluster_id 和 kafka_node_id,还可以用 client_id 加以丰富,这样来自不同应用的指标就不会混成一条匿名的数据流。
最后这一点非常重要。
如果两个生产者推送了同名的指标,我们就需要知道哪个是哪个。默认情况下,reporter 会把 client id 作为标签加入。而更敏感或高基数的标签,例如 client instance id、principal 或 client address,则需要显式开启。
工作原理
Broker 端的部分是通过 Kafka 的 ClientTelemetry 接口实现的。
当一个支持 KIP-714 的客户端向 broker 推送遥测数据时,插件会收到 OTLP 负载。请求处理线程只做最少量的工作:它把负载复制到一个有界的内存队列中。随后,一个守护线程从该队列读取数据,将负载转换为 OpenTelemetry SDK 指标数据,用标签加以丰富,再通过一个专用的指标导出器将其导出。
这与 reporter 其余部分秉持的设计原则相同:
Kafka 绝不能被可观测性流水线阻塞。
如果 collector 不可用、变慢或配置有误,Kafka 应当继续处理流量。这批遥测数据可以被丢弃。Kafka 的延迟不应依赖于监控后端。
客户端遥测接收器也有自己的自监控指标:
monedula_reporter_clienttelemetry_received_total
monedula_reporter_clienttelemetry_forwarded_total
monedula_reporter_clienttelemetry_dropped_total
monedula_reporter_clienttelemetry_unsupported_metrics_dropped_total
monedula_reporter_clienttelemetry_queue_depth
因此,如果客户端遥测数据停止流入,你在盲目猜测之前,就有可以查看的线索。
安装
该构件已发布到 Maven Central,因此无需手动构建。
对于版本 0.10.0,从 Maven Central 下载 JAR:
curl -L \
-o monedula-metrics-reporter-0.10.0.jar \
https://repo1.maven.org/maven2/dev/monedula/monedula-metrics-reporter/0.10.0/monedula-metrics-reporter-0.10.0.jar
然后将其复制到 Kafka 的 classpath 中:
cp monedula-metrics-reporter-0.10.0.jar $KAFKA_HOME/libs/
之后,配置 broker:
metric.reporters=dev.monedula.metricsreporter.OtlpMetricReporter
otlp.metric.reporter.endpoint=http://otel-collector:4317
otlp.metric.reporter.transport=grpc
这会为 Kafka SPI 指标、Yammer broker 指标和客户端遥测启用该 reporter。
客户端遥测在 broker 上默认是启用的:
otlp.metric.reporter.client.telemetry.enabled=true
你也可以显式地将其禁用:
otlp.metric.reporter.client.telemetry.enabled=false
禁用后,broker 将不再向客户端通告遥测能力。
Collector 配置
一份最小化的 OpenTelemetry Collector 配置就能接收来自 reporter 的 OTLP 数据,并将指标暴露给 Prometheus:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
resource_to_telemetry_conversion:
enabled: true
service:
pipelines:
metrics:
receivers: [otlp]
exporters: [prometheus]
在使用 Prometheus 时,resource_to_telemetry_conversion 这一设置很重要。它会把 OpenTelemetry 的资源属性提升为 Prometheus 标签。
如果没有它,kafka_cluster_id、kafka_node_id 和 client_id 等标签可能不会出现在你期望的位置。
之后 Prometheus 便可以抓取该 collector:
scrape_configs:
- job_name: otel-collector
static_configs:
- targets: ["otel-collector:8889"]
关键部分:客户端指标订阅
仅仅安装插件是不够的。
这是 KIP-714 中很容易被忽略的一环。
只有当集群上配置了客户端指标订阅时,客户端才会推送指标。
例如:
kafka-client-metrics.sh \
--bootstrap-server broker:9092 \
--alter \
--name all-clients \
--metrics "*" \
--interval 5000
这会创建或更新一份名为 all-clients 的订阅,请求所有可用的客户端指标,并将推送间隔设为 5 秒。
在生产环境中,采集全部指标可能太多了。你也可以只订阅所选的指标前缀。
例如,你可以从标准的 Kafka 客户端指标开始:
kafka-client-metrics.sh \
--bootstrap-server broker:9092 \
--alter \
--name standard-client-metrics \
--metrics "org.apache.kafka." \
--interval 10000
具体的订阅策略取决于你想观测什么,以及你能接受多大的指标量。
重要的是,订阅存在于 broker 端。这使得 Kafka 运维人员可以自行决定应采集什么,而无需要求每个应用团队去改动他们的监控配置。
客户端配置又该如何?
对于支持 KIP-714 的客户端,指标推送通常是默认启用的。
不过,你仍可以将其显式设置:
enable.metrics.push=true
对于 Java 客户端,这属于 Kafka 客户端配置的一部分。
对于基于 librdkafka 的客户端(例如 confluent-kafka-python),思路是相同的。例如:
Producer({
"bootstrap.servers": "broker:9092",
"client.id": "orders-producer",
"enable.metrics.push": True,
})
对于消费者:
Consumer({
"bootstrap.servers": "broker:9092",
"group.id": "orders-consumer-group",
"client.id": "orders-consumer",
"enable.metrics.push": True,
})
client.id 值得刻意设置。
默认情况下,reporter 会把它加入转发的客户端指标,这样仪表盘就能按应用区分指标。若没有一个有意义的 client id,你仍然能拿到指标,但在排查问题时会难用得多。
丰富化选项
默认情况下,转发的客户端指标会用 broker 身份和 client id 加以丰富:
otlp.metric.reporter.client.telemetry.enrich.broker=true
otlp.metric.reporter.client.telemetry.enrich.client.id=true
这意味着客户端指标可以按集群、broker 和应用进行分组。
此外还有一些可选的丰富化项:
otlp.metric.reporter.client.telemetry.enrich.client.instance.id=true
otlp.metric.reporter.client.telemetry.enrich.client.identity=true
当你想区分各个正在运行的客户端实例时,client instance id 很有用,但它是高基数的。
Client identity 会加入 principal 和 client address。这在受控环境中会非常有用,但也可能包含敏感信息,因此默认是禁用的。
一句话概括:先从 broker 标签和 client id 开始,只有在真正需要时才启用其余项。
快速上手
代码仓库中包含一个快速上手环境,内置了 Kafka、OpenTelemetry Collector、Prometheus 和 Grafana。
启动快速上手环境:
cd quickstart
docker compose up -d
该演示服务会创建一份 KIP-714 客户端指标订阅,并启动生产者和消费者的流量。因此在整套环境启动后,broker 仪表盘和客户端仪表盘都应开始接收数据。
Grafana 的访问地址为:
http://localhost:3000
Prometheus 的访问地址为:
http://localhost:9090
该快速上手环境使用以下命令创建订阅:
kafka-client-metrics.sh \
--bootstrap-server kafka1:9092 \
--alter \
--name quickstart-clients \
--metrics "*" \
--interval 5000
这是刻意保持简单:采集全部指标,每 5 秒推送一次,让仪表盘迅速活跃起来。
对于真实环境,我通常会更谨慎地起步。先采集标准指标,验证指标量,然后在需要时再增加更多指标。
Java 客户端与其他客户端
KIP-714 有一个有趣之处:它创建了一套通用机制,但并非每个客户端库都暴露相同深度的指标。
Java 客户端发送的指标集合最为丰富。它可以从内部注册表推送许多指标,包括更丰富的生产者和消费者细节:批处理、缓冲区使用、请求耗时、按 topic 的细分、消费者的 lag 和 lead,以及其他 Java 客户端特有的信号。
其他客户端库可能发送较小的集合。
这并不意味着 KIP-714 只对 Java 客户端有用。为所有支持它的客户端提供一条通用的、由 broker 驱动的采集路径,本身就非常有价值。但当你查看仪表盘时,应当记住:面板缺失并不总是意味着出了问题。
有时只是这个客户端库里根本不存在该指标。
这正是为什么新的 Grafana 仪表盘被拆分为两个部分:
- 标准面板
- 扩展面板
标准面板应当适用于每一个暴露了相关通用指标、且符合 KIP-714 的客户端。
扩展面板则主要对 Java 客户端有用,因为 Java 客户端目前提供的指标集合最为丰富。
这种拆分是刻意为之的。我不希望一个仪表盘仅仅因为某个非 Java 客户端没有暴露 Java 特有的指标就显得像坏掉了一样。把这条边界显示出来会更好。
新增仪表盘
新版本增加了两个以客户端为中心的仪表盘:
- Kafka Producers(客户端)
- Kafka Consumers(客户端)
生产者仪表盘聚焦于生产记录的客户端侧视角:请求延迟、限流、排队、批处理、错误和吞吐量。
消费者仪表盘聚焦于消费记录的客户端侧视角:fetch 延迟、提交速率、poll 行为、与 rebalance 相关的信号、分配变更,以及在可用时的消费者侧 lag/lead。
两个仪表盘都包含按集群和 client id 的过滤器。在指标集合支持的情况下,它们还包含按 topic 或 group 级别的下钻。


关于这套方案,有一点我非常喜欢:现在同一个 Grafana 文件夹就能同时呈现两侧的情况。
Broker 仪表盘回答:
集群是否健康?
客户端仪表盘回答:
应用是如何体验这个集群的?
而由于两者都来自同一条 OTLP 流水线、拥有一致的标签,将它们关联起来就变得容易得多。
总结
KIP-714 是 Kafka 可观测性中非常重要的一步。
它把客户端指标从每个应用团队各自独立暴露的东西,变成了 Kafka 集群可以集中请求的东西。
monedula-metrics-reporter 现在支持了这一模式。
它仍然从两个 Kafka 指标注册表中导出 broker 指标。但现在它还会接收来自 KIP-714 客户端推送的 OTLP 遥测数据,并将其转发到同一个 OpenTelemetry Collector。
因此,该插件现在覆盖了三种来源:
- Kafka SPI 指标
- Yammer broker 指标
- KIP-714 客户端遥测
结果是一套更简单的 Kafka 监控架构:broker 上一个插件、一条 OTLP 流水线,以及同时覆盖 broker 侧和客户端侧行为的仪表盘。
这些代码是开源的,可在 GitHub 上获取