grpc-go 服务端指标被记成下游方法,检查共享 context 的插桩回归
grpc-go 1.80.0 的 OpenTelemetry 插桩可能让下游调用覆盖入口 RPC 信息。1.81.1 已分开两侧统计,修复时仍要保留取消与 deadline。
Go 的 gRPC 服务在处理请求时又调用了下游 gRPC。调用本身成功,监控里的 grpc.server.call.duration 却把 grpc.method 记成下游方法,入口方法的延迟和数量像是消失了。
在 grpc-go 1.80.0 的 OpenTelemetry 集成中,传给下游的 context 会让客户端与服务端统计复用同一份 RPC 信息。客户端填写自己的方法和开始时间时,可能覆盖服务端尚未记录完的数据。这里要修正的是统计上下文隔离,不能据指标错位就认定请求被路由到了错误服务。
一个请求里有两种 RPC 身份
例如入口是 /shop.OrderService/Create,处理过程中调用 /stock.InventoryService/Reserve。最终应有两组不同的统计:服务端指标属于 OrderService/Create,客户端指标属于 InventoryService/Reserve,各自计算自己的时长。
故障路径通常同时配置了 gRPC 的 OpenTelemetry server option 与 dial option,并把传入的服务端 context 传给客户端。这种传递本来有用途:保留截止时间、取消和链路信息。若只把下游调用改成 context.Background(),虽然可能不再复用那份统计数据,也会切断这些信息。
先核对实际构建的模块:
go list -m -json google.golang.org/grpc
OpenTelemetry 插桩包位于这个模块的 stats/opentelemetry 中。还要检查构建清单、replace 指令及最终二进制,确认运行的应用与本地依赖一致。
观察原始指标标签,区分服务端 grpc.server.call.duration 与客户端 call/attempt 指标。不同方法的聚合、监控查询的标签重写和高基数过滤,也可能让图表看起来串号;应先确认导出前的数据已经出现覆盖。
使用隔离客户端与服务端 RPC 信息的版本
grpc-go 1.81.1 的正式发行记录明确包含客户端与服务端 RPC 信息分离的修复。1.82.0 的对应源码使用独立的 clientRPCInfoKey 与 serverRPCInfoKey,不再从相同 context 键复用两侧 attemptInfo。
采用项目允许的兼容版本,并让 gRPC、OpenTelemetry SDK 与其他相关组件保持相容。升级可能同时带来最低 Go 版本和其他行为变化,需要按依赖约束处理,不在生产机器临时修改内部 context key 或 vendor 指针。
升级后保留正常的 context 传递。下面仅展示已有处理函数中的下游调用形态;inventoryClient、请求与超时值由应用提供:
childCtx, cancel := context.WithTimeout(incomingCtx, downstreamTimeout)
defer cancel()
reply, err := inventoryClient.Reserve(childCtx, reserveRequest)
派生 context 可以收紧下游截止时间,并继续传播上游取消;它不是这个旧版本统计覆盖的可靠修复。WithTimeout 不会深拷贝 context 里指向的可变对象,所以单纯多加一层 context 仍可能继承受影响的 RPC 信息。
验证时,用同一入口分别调用一个下游、多个不同下游,并覆盖下游失败及上游取消。检查入口方法、下游方法、状态和时长分别归属正确,同时确认截止时间与取消仍按业务规则传播。只确认总请求数相等,无法证明方法标签没有被覆盖。
若更新后仅面板仍显示旧标签,继续核对采集与存储的聚合窗口;若新导出的原始点仍错位,则检查应用是否重复注册了不同版本的插桩。保留依赖锁、插桩设置和导出样本,回退时恢复配套组件,不用删掉所有监控来掩盖错误。


