GRPC拦截器需显式注册才生效:服务端用grpc.UnaryInterceptor/grpc.StreamInterceptor传给grpc.NewServer(),客户端用grpc.WithUnaryInterceptor/grpc.WithStreamInterceptor传给grpc.Dial();未注册则拦截器完全不触发。

GRPC拦截器函数怎么注册才能生效
GRPC Go 客户端和服务端的拦截器必须显式传入,不注册就完全不触发。服务端用 grpc.UnaryInterceptor 或 grpc.StreamInterceptor 作为选项传给 grpc.NewServer();客户端则需在 grpc.Dial() 时通过 grpc.WithUnaryInterceptor 或 grpc.WithStreamInterceptor 注册。
常见错误是只写了拦截器函数,但没传进 Server/Client 实例 —— 此时代码能跑、日志没输出、耗时统计全丢弃。
- 服务端拦截器必须放在所有其他
grpc.ServerOption之前或之后都行,但不能遗漏 - 客户端若同时使用多个拦截器(比如鉴权 + 监控),注意执行顺序:
WithUnaryInterceptor的调用顺序即拦截器链顺序 - 流式拦截器(
StreamInterceptor)和一元拦截器(UnaryInterceptor)互不替代,监控耗时必须分别实现
耗时监控拦截器里如何安全获取方法名和耗时
GRPC 的上下文参数里不直接暴露方法名,得从 info.FullMethod 提取;耗时计算不能依赖 time.Since() 粗略包裹整个 handler,因为 handler 可能异步或提前返回 —— 必须用 defer 在函数退出时读取结束时间。
示例服务端拦截器片段:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
func durationInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
start := time.Now()
defer func() {
duration := time.Since(start).Milliseconds()
log.Printf("method=%s, duration=%.2fms", info.FullMethod, duration)
}()
return handler(ctx, req)
}
-
info.FullMethod格式为"/package.ServiceName/MethodName",可按需截取或打标 - 不要在 defer 里做阻塞操作(如写磁盘、发 HTTP 请求),否则拖慢 RPC 响应
- 如果 handler panic,
err会是nil,但resp也是nil,此时仍要记录耗时(panic 发生前已耗时)
为什么客户端拦截器统计的耗时比服务端高很多
客户端测的是“发起请求到收到响应”的全程,包含网络往返、序列化、服务端处理、反序列化;服务端测的只是 handler 执行时间。两者差值通常就是网络开销 + 编解码时间 —— 这不是 bug,是设计使然。
若发现客户端耗时异常高(比如比服务端高 500ms+ 且稳定复现),优先排查:
- 客户端是否启用了
grpc.WithBlock()且 DNS 解析慢(尤其 Kubernetes 内部域名) - 是否未设置
grpc.WithTimeout()导致重试拉长总耗时 - 服务端日志显示 handler 耗时正常,但客户端超时 —— 很可能是 TLS 握手或连接池复用问题
想对齐观测维度,可在客户端拦截器里也提取 info.FullMethod,和服务端日志字段保持一致,方便关联分析。
生产环境加监控拦截器要注意什么
高频接口每秒几千次调用时,简单 log.Printf 会成为性能瓶颈,且日志刷屏难以聚合。真正可用的方案得兼顾低侵入、可采样、易对接监控系统。
- 用结构化日志库(如
zap)替代fmt.Printf,避免字符串拼接开销 - 加采样开关:比如只记录 P95 以上耗时,或固定 1% 请求全量采集,用
rand.Float64() 控制 - 别把监控逻辑和业务逻辑耦合 —— 拦截器里只负责采集,上报交给独立 goroutine 或 metrics client(如
prometheus.Counter) - 注意 context 传递:如果下游服务也用 GRPC,可透传 traceID(通过
metadata.MD),否则各段耗时无法串联
最常被忽略的一点:拦截器函数本身不能 panic,否则整个 RPC 会失败。务必用 recover() 包裹关键逻辑,尤其涉及第三方 metrics 上报时。

















