KubeSphere 技术栈中的 gRPC Prometheus 监控拦截器:go-grpc-prometheus 实战指南
【免费下载链接】kubespherekubesphere/kubesphere: KubeSphere 是一个开源的企业级容器平台,构建于 Kubernetes 之上,提供全栈化容器管理能力,包括服务治理、DevOps、微服务治理、监控告警、日志查询等功能,旨在帮助企业快速构建云原生应用和实现数字化转型。项目地址: https://gitcode.com/kubesphere/kubesphere
导读
go-grpc-prometheus 是 gRPC Go 生态中官方推荐的 Prometheus 监控中间件,通过 gRPC 拦截器(Interceptor)机制,为 gRPC 服务端与客户端自动采集请求量、消息量、处理时长等关键指标,无需侵入业务代码。本文以 vendor/github.com/grpc-ecosystem/go-grpc-prometheus/README.md 为核心,结合该库在 KubeSphere 仓库 vendor 目录下的完整源码,系统讲解其工作原理、接入方式、指标体系与 PromQL 查询实践,帮助你为任何 gRPC 服务搭建可观测、可告警的监控体系。读完本文,你将掌握:如何用三行代码为 gRPC 服务端/客户端启用监控、如何理解并定制grpc_server_*/grpc_client_*指标、如何正确开启延迟直方图,以及如何编写 SLO 级别的监控告警查询。
背景:为什么用 gRPC 拦截器做监控
gRPC 是云原生领域最主流的 RPC 框架之一。gRPC Go(google.golang.org/grpc)提供了拦截器(Interceptor)机制——一种在请求进入用户业务逻辑之前由 gRPC Server 执行的中间件。拦截器是落地通用横切逻辑(认证、日志、监控)的标准方式:业务代码不需要埋点,拦截器统一负责采集。
go-grpc-prometheus 正是基于这一机制实现的监控库。它提供两类拦截器:服务端(Server-side)与客户端(Client-side),分别覆盖 gRPC 服务端与调用方两侧的可观测性。当需要多个拦截器链式组合时,可参考go-grpc-middleware项目(该库不在本仓库内,此处仅作背景说明)。
从 KubeSphere 的 go.mod 可以看到,本项目以github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0(间接依赖)引入该库,用于支撑集群内部 gRPC 组件的监控能力。
服务端接入:三行代码启用 gRPC 监控
服务端接入分为三步:创建 gRPC Server 时挂载拦截器 → 注册业务服务 → 调用Register预初始化指标。完整流程如下:
import ( "net/http" "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "google.golang.org/grpc" ) // 1. 初始化 gRPC server 的拦截器。 myServer := grpc.NewServer( grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), ) // 2. 注册你的 gRPC 服务实现。 myservice.RegisterMyServiceServer(myServer, &myServiceImpl{}) // 3. 所有服务注册完成后,确保所有 Prometheus 指标都被初始化。 grpc_prometheus.Register(myServer) // 4. 注册 Prometheus 指标暴露端点。 http.Handle("/metrics", promhttp.Handler())其中:
UnaryServerInterceptor负责一元(Unary)RPC 的监控,即"单请求-单响应"调用;StreamServerInterceptor负责流式(Streaming)RPC 的监控,包括客户端流、服务端流与双向流;grpc_prometheus.Register(myServer)必须放在所有服务注册完成之后调用。其底层实现是 server.go 中的Register函数,它调用DefaultServerMetrics.InitializeMetrics(server),遍历server.GetServiceInfo()返回的全部服务与方法,为每个方法预创建值为 0 的指标序列(详见下文"预注册机制"),避免 Prometheus 中出现指标缺失。
/metrics端点通过promhttp.Handler()暴露,Prometheus 服务端只需在抓取配置中指向该地址即可采集。
客户端接入:监控所有外部 RPC 调用
客户端接入同样简单,在grpc.Dial时挂载两个拦截器即可:
import ( "github.com/grpc-ecosystem/go-grpc-prometheus" ) clientConn, err := grpc.Dial( address, grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor), grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor), ) client := pb_testproto.NewTestServiceClient(clientConn) resp, err := client.PingEmpty(ctx, &myservice.Request{Msg: "hello"})客户端指标与服务端指标是"镜像"关系:服务端记录收到请求的时刻,客户端记录发出请求的时刻;两者结合可以还原一次 RPC 的完整链路,包括网络传输耗时(客户端总耗时减去服务端处理耗时)。
指标体系:标签、计数器与直方图
指标命名与子系统
所有服务端指标以grpc_server作为 Prometheus subsystem 名称,所有客户端指标以grpc_client开头,两者概念一一镜像。完整指标定义见 server_metrics.go 与 client_metrics.go。
核心标签(Labels)
所有指标都携带三组丰富的标签:
| 标签 | 含义 | 示例 |
|---|---|---|
grpc_service | gRPC 服务名,由 protobufpackage与service段组合而成 | grpc_service="mwitkow.testproto.TestService" |
grpc_method | 被调用的方法名 | grpc_method="Ping" |
grpc_type | RPC 请求类型,对延迟测量尤其关键 | unary/client_stream/server_stream/bidi_stream |
四种grpc_type定义在 util.go 中:
unary:单请求、单响应 RPC;client_stream:多请求、单响应 RPC(客户端流);server_stream:单请求、多响应 RPC(服务端流);bidi_stream:多请求、多响应 RPC(双向流)。
服务名与方法名由拦截器从 gRPC 的 full method 字符串中解析得到,解析逻辑见 util.go 的splitMethodName:去掉前导/后按/分割,前半段为服务名、后半段为方法名,无法解析时返回"unknown"。
对于已完成(handled)的 RPC,还会附加:
| 标签 | 含义 | 常见取值 |
|---|---|---|
grpc_code | 人类可读的 gRPC 状态码 | OK(成功)、InvalidArgument(参数非法)、Internal(服务端内部错误,不向客户端披露细节)等 |
计数器(Counters):一次完整 RPC 的生命周期
以服务端为例,假设我们跟踪mwitkow.testproto.TestService服务上的一次PingList调用(服务端流,成功返回 20 条消息),四个计数器依次变化:
1. 请求到达,grpc_server_started_total自增 1,并启动处理计时(若直方图已启用):
grpc_server_started_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 12. 用户逻辑收到客户端消息,grpc_server_msg_received_total自增 1:
grpc_server_msg_received_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 13. 用户逻辑逐条返回消息,每发送 1 条grpc_server_msg_sent_total自增 1,20 条消息累计为:
grpc_server_msg_sent_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 204. 调用结束后,按最终状态码(OK或其它 gRPC 状态码)更新grpc_server_handled_total:
grpc_server_handled_total{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1从源码看,上述递增动作全部封装在 server_reporter.go 的serverReporter中:创建 reporter 时递增started计数并记录startTime;ReceivedMessage/SentMessage分别递增收发消息计数;Handled(code)在调用结束后递增handled计数并(可选)观测直方图。客户端侧对应逻辑在 client_reporter.go 中实现。
对于流式 RPC,收发消息的计数是通过包装流对象实现的:服务端拦截器将原始grpc.ServerStream包装为monitoredServerStream(见 server_metrics.go),在SendMsg/RecvMsg成功时自动递增计数;客户端侧同理,且客户端流在收到io.EOF时按OK状态码结束计数(见 client_metrics.go)。
直方图(Histograms):延迟分布与 SLO
Prometheus 直方图是衡量 RPC 延迟分布的最佳工具,但高基数的直方图指标对 Prometheus 的存储与查询开销很大,因此延迟监控默认关闭。需要在服务端初始化代码中显式开启:
grpc_prometheus.EnableHandlingTimeHistogram()开启后,每次调用完成时的处理时长会记录到grpc_server_handling_seconds直方图。该直方图包含三个子指标:
grpc_server_handling_seconds_count:按状态和方法统计的已完成 RPC 总数;grpc_server_handling_seconds_sum:按状态和方法累计的处理时长,可用于计算平均处理时间;grpc_server_handling_seconds_bucket:按状态和方法落入各延迟桶的 RPC 计数,可用于 SLO 估算。
启用后的采样输出如下:
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.005"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.01"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.025"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.05"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.1"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.25"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.5"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="1"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="2.5"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="5"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="10"} 1 grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="+Inf"} 1 grpc_server_handling_seconds_sum{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 0.0003866430000000001 grpc_server_handling_seconds_count{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1默认桶(bucket)取自 Prometheus 的prom.DefBuckets,见 server_metrics.go。注意:直方图不带grpc_code标签(只有grpc_type、grpc_service、grpc_method),因为按"是否成功"拆分延迟分布并无业务意义,还能有效控制基数。
客户端侧镜像指标
客户端指标与服务端完全镜像:grpc_client_started_total、grpc_client_msg_received_total、grpc_client_msg_sent_total、grpc_client_handled_total,以及通过EnableClientHandlingTimeHistogram()开启的grpc_client_handling_seconds直方图。启用与定制方式与服务端一致。
进阶定制:直方图选项、常量标签与自定义注册表
go-grpc-prometheus 提供了灵活的可配置能力,全部定义在 metric_options.go 中:
自定义直方图桶(Buckets)——当默认桶不满足业务延迟分布时,可通过WithHistogramBuckets定制:
grpc_prometheus.EnableHandlingTimeHistogram( grpc_prometheus.WithHistogramBuckets([]float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2.5, 5}), )为计数指标添加常量标签——使用WithConstLabels可为所有 Counter 指标附加固定标签(如环境标识):
metrics := grpc_prometheus.NewServerMetrics( grpc_prometheus.WithConstLabels(prometheus.Labels{"env": "prod"}), )使用自定义 Prometheus 注册表——默认情况下,库的init()函数会把指标注册到全局默认注册表(见 server.go 与 client.go)。当你想精确控制哪些指标进入哪个注册表时,应通过NewServerMetrics/NewClientMetrics创建独立实例,再手动Register到目标注册表,而不是使用包级默认实例。
预注册机制:为什么指标不会"缺失"
Prometheus 的 Counter/Histogram 是惰性创建的:如果没有请求到达,某个标签组合对应的指标序列就不会存在。这会给监控带来困扰——告警查询在指标缺失时无法计算。go-grpc-prometheus 通过Register解决此问题:
server_metrics.go 中的InitializeMetrics遍历server.GetServiceInfo(),对每个已注册服务的每个方法,预先为grpc_server_started_total、grpc_server_msg_received_total、grpc_server_msg_sent_total创建值为 0 的序列,并为全部 17 种 gRPC 状态码(定义于 util.go)预创建grpc_server_handled_total的零值序列。
这一设计的直接收益是:服务上线后指标即刻完整可见,查询与告警从第一分钟起就是准确的,不会因"指标尚未出现"而产生误报或漏报。
监控查询实战:从原始指标到 SLO 告警
Prometheus 的设计哲学是"上报原始指标,聚合交给监控系统完成"。上述指标的细粒度标签让聚合查询变得非常灵活。以下查询示例均以job="foo"作为 Prometheus 抓取任务的通用标签。注意:对某服务所有方法求和时省略grpc_method标签即可。
1. 请求入站速率(RPS)——按服务统计每分钟请求速率:
sum(rate(grpc_server_started_total{job="foo"}[1m])) by (grpc_service)2. 一元请求错误率——按服务统计非OK结束的一元 RPC 速率:
sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service)3. 一元请求错误百分比——结合上述两个查询计算失败率,是 SLA 告警(如"失败率不超过 1%")的典型表达式:
sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service) / sum(rate(grpc_server_started_total{job="foo",grpc_type="unary"}[1m])) by (grpc_service) * 100.04. 平均响应流大小——统计服务端流 RPC 平均返回的消息条数。除数使用"已启动"的 RPC 数而非"已完成"数,以计入进行中的请求:
sum(rate(grpc_server_msg_sent_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service) / sum(rate(grpc_server_started_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service)该指标可用来追踪系统返回的流大小,例如发现客户端开始发起"宽"查询(返回大量消息)的时间点。
5. 一元请求 P99 延迟——基于直方图桶估算 99 分位处理时长,采用滚动 5 分钟窗口。配合 50%、90% 分位可全面洞察系统响应性(如缓存命中对延迟的影响):
histogram_quantile(0.99, sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary"}[5m])) by (grpc_service,le) )6. 慢查询占比(>250ms)——由于 Prometheus 桶是le(小于等于)语义,直接统计"快请求"占比更简单,再用数学换算得到慢请求百分比。可作为 SLA 告警(如"慢于 250ms 的请求占比低于 1%"):
100.0 - ( sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary",le="0.25"}[5m])) by (grpc_service) / sum(rate(grpc_server_handling_seconds_count{job="foo",grpc_type="unary"}[5m])) by (grpc_service) ) * 100.0最佳实践与注意事项
- 默认关闭直方图是有意为之:延迟直方图是典型的高基数高开销指标。在开启
EnableHandlingTimeHistogram前,先确认 Prometheus 实例的容量与保留策略,或通过WithHistogramBuckets压缩桶数量。 - 务必调用
Register(myServer):不调用则无法预注册零值指标,服务低流量期的查询与告警会因指标缺失而不准确。 - 区分
grpc_type:一元与流式 RPC 的延迟语义差异显著,查询和告警时务必带上grpc_type过滤,避免混入错误样本。 - 客户端与服务端指标配合使用:
grpc_client_handling_seconds与服务端grpc_server_handling_seconds的差值可以估算网络传输与排队耗时,是定位链路瓶颈的有力手段。 - 版本与依赖:本仓库通过 go.mod 以
v1.2.0间接依赖方式引入该库;若在自有项目中直接使用,应将其提升为直接依赖并固定版本。
结语
go-grpc-prometheus 以极低的接入成本,为 gRPC 服务提供了标准、完整的 Prometheus 监控能力。通过拦截器这一 gRPC 原生机制,它做到了"零侵入业务代码、全链路可观测":服务端与客户端的镜像指标还原了 RPC 的完整生命周期,细粒度的grpc_service/grpc_method/grpc_type/grpc_code标签支撑起从 RPS、错误率、延迟分位到 SLO 告警的全套监控实践。对运行在 KubeSphere 平台上的 gRPC 微服务而言,这套指标可直接对接平台的监控告警体系,是构建云原生可观测性基座的重要一环。本文涉及的源码均可在本仓库 vendor/github.com/grpc-ecosystem/go-grpc-prometheus 目录下查阅。
【免费下载链接】kubespherekubesphere/kubesphere: KubeSphere 是一个开源的企业级容器平台,构建于 Kubernetes 之上,提供全栈化容器管理能力,包括服务治理、DevOps、微服务治理、监控告警、日志查询等功能,旨在帮助企业快速构建云原生应用和实现数字化转型。项目地址: https://gitcode.com/kubesphere/kubesphere
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考