必须全局复用*grpc.clientconn实例,否则高频调用会引发time_wait暴增、qps卡在3000、延迟毛刺;同时需启用keepalive防代理静默断连,并确保http/2真正启用、protobuf序列化优化及context正确透传。

gRPC连接不复用导致TIME_WAIT暴增
高频调用下出现大量TIME_WAIT状态、QPS卡在3000左右、延迟毛刺明显——八成是客户端每次请求都grpc.Dial()又conn.Close()。这个操作本身包含DNS解析、TLS握手、HTTP/2协商,开销远超一次RPC。
- 必须全局复用同一个
*grpc.ClientConn实例,初始化后注入依赖,不要在handler里临时创建 - 服务端要启用Keepalive:用
grpc.KeepaliveParams()设Time(如25s)和Timeout(如10s),防中间代理(Nginx/Envoy)静默断连 - Nginx反向代理gRPC时,必须用
grpc_pass而非proxy_pass,且配置http2;否则gRPC降级为HTTP/1.1,失去多路复用优势
HTTP/2未启用或被降级
明明用了gRPC,压测结果却和JSON HTTP差不多——大概率是HTTP/2没真正跑起来。gRPC底层依赖HTTP/2的多路复用和头部压缩,一旦降级,性能直接打五折。
- 服务端监听必须显式支持HTTP/2:Go 1.19+默认开启,但若套了Nginx,需确认其
http2指令已启用,且ssl_protocols包含TLSv1.2及以上 - 客户端发起
grpc.Dial()时,地址必须带https://或明确启用TLS;用insecure.NewCredentials()仅限本地调试,生产环境必须配真实证书 - 用
curl -v https://your-service:port看响应头是否有HTTP/2 200;或抓包确认ALPN协商是否成功
Protobuf序列化成为CPU瓶颈
QPS过万后CPU飙升、P99延迟跳变,profile里proto.Marshal和runtime.mallocgc占比高——说明序列化正在吃掉算力。
- 避免用官方
protoc-gen-go生成代码;换成gogo/protobuf或bufbuild/protoc-gen-go,生成带MarshalUnsafe和预计算Size的方法 - 对心跳、状态上报等高频小消息,手动实现
encoding.BinaryMarshaler,绕过protobuf反射开销 - proto message里别放
bytes大字段(如base64图片);改用bytes+ 客户端预分配make([]byte, 0, 4096)复用缓冲区
context超时误设引发雪崩
上游接口200ms SLA,下游gRPC调用却频繁超时、重试、级联失败——问题常出在context.WithTimeout用错了地方。
- 绝对不要在handler开头就
ctx, cancel := context.WithTimeout(r.Context(), 150*time.Millisecond)再传给gRPC;这会把上游剩余时间砍掉,下游实际可用时间可能只剩20ms - 正确做法:直接透传
r.Context(),让gRPC自动继承deadline;仅对DB查询、缓存加载等内部逻辑,才用context.WithTimeout,且timeout ≤ 上游剩余时间的1/3 - 检查所有
client.Call(ctx, req)调用点,确保ctx来自HTTP入口,没被无意替换为context.Background()
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











