根本原因是gRPC服务未启用http/1.1的/metrics接口,需另起独立HTTP服务器(如:9091)挂载promhttp.Handler(),并确保prometheus targets指向该端口而非gRPC端口。

gRPC服务暴露/metrics端点但Prometheus抓不到数据
根本原因通常是gRPC服务没启用HTTP/1.1的metrics接口——gRPC本身走HTTP/2,而Prometheus默认用HTTP/1.1抓取/metrics。必须额外起一个独立的HTTP服务器(比如用http.ServeMux)来暴露指标。
- 别把
promhttp.Handler()注册到gRPC的Server上,它不处理HTTP请求 - 用
net/http另起一个端口(如:9091),挂载promhttp.Handler() - 确保防火墙或k8s Service允许该端口入站,Prometheus配置里的
targets要指向这个HTTP端口,不是gRPC端口 - 如果用了gRPC-gateway,也别指望它自动透传
/metrics——它只转译POST/GET等业务路径
统计gRPC方法耗时,但histogram_bucket标签里没有method名
默认的grpc_server_handled_histogram_seconds指标确实不含method标签,因为官方go-grpc-prometheus库早期版本为减少cardinality主动去掉了它;新版本虽支持,但需手动开启且注意性能影响。
- 升级到
github.com/grpc-ecosystem/go-grpc-prometheus@v2.1.0+(v2+才支持method标签) - 初始化时显式调用
EnableHandlingTimeHistogram(),并传入WithHistogramBuckets(...)和WithHistogramConstLabels(...) - 加
WithSubsystem("grpc")避免指标名冲突,否则可能和client端指标重名 - 注意:开启
method标签后,每个gRPC方法都会生成独立时间序列,QPS高时易触发Prometheus内存/存储压力
P99耗时查询结果为空或恒为0
不是Prometheus没采集到,而是直查histogram_quantile(0.99, ...)时,底层bucket数据未覆盖实际耗时范围,或者rate窗口太短导致无样本。
- 确认你的
grpc_server_handled_histogram_seconds_bucket的le标签最大值是否大于真实P99(比如最高只到le="2",但实际有3秒请求,那P99就永远算不准) - 查询时用
rate(grpc_server_handled_histogram_seconds_bucket[5m])而非原始计数,否则quantile会失效 - 避免用太小的range vector(如
[1m]),尤其在低流量服务上,可能整个窗口内都没几个样本 - 检查是否漏掉了
grpc_server_handled_histogram_seconds_count和_sum,这两个是histogram_quantile内部依赖的隐含指标
Go服务里同时用grpc.Server和http.Server,指标重复上报
当两个server共用同一组Prometheus注册器(prometheus.Defaultregisterer)且都注册了相同指标(比如都调了promhttp.InstrumentHandlerDuration),会导致duplicate metrics错误,Prometheus抓取失败。
- 给gRPC和HTTP分别创建独立的
*prometheus.Registry,不要共享DefaultRegisterer - gRPC指标用
grpc_prometheus.Register(server, grpc_prometheus.WithServerCounter(...))注册到自定义registry - HTTP指标用
promhttp.HandlerFor(registry, promhttp.HandlerOpts{})绑定对应registry - 启动HTTP server时,确保只暴露你明确注册过的那个registry,别误把
prometheus.DefaultGatherer塞进去
真正麻烦的是跨goroutine的指标生命周期管理——比如某个metric在shutdown时被多次Unregister,会panic。得靠sync.Once或显式控制注册时机,这点容易被忽略。