必须为微服务调用设置 context.WithTimeout,复用 HTTP/gRPC 客户端,避免 JSON 标准库序列化,精简中间件链路,并重视 DNS 与 TLS 复用。

用 context.WithTimeout 主动控制调用生命周期
微服务间 HTTP 或 gRPC 调用没有超时,是延迟飙升最常见的根源。Go 默认不设限,一次卡住的后端会拖垮整个调用链。
必须在每次发起请求前绑定明确的 context.Context,尤其避免直接传 context.Background():
-
http.Client的Do方法必须传带超时的ctx,不能只靠client.Timeout(它只作用于连接+首字节,不覆盖响应读取) - gRPC 客户端调用时,
ctx需通过grpc.CallOption传入,如grpc.WaitForReady(false)配合超时可防止阻塞等待连接就绪 - 超时值不能拍脑袋定:建议按 P95 延迟 × 2~3 倍设置,同时预留至少 100ms 给网络抖动
复用 http.Client 和 grpc.ClientConn 实例
每次调用都新建 http.Client 或重连 grpc.ClientConn,会触发 DNS 查询、TLS 握手、TCP 建连,延迟直接跳到百毫秒级。
正确做法是全局复用,并配合适当参数:
立即学习“go语言免费学习笔记(深入)”;
-
http.Client复用时,Transport必须自定义:启用MaxIdleConns(如 100)、MaxIdleConnsPerHost(如 100)、IdleConnTimeout(如 30s) - gRPC 连接复用要求更严格:一个
*grpc.ClientConn应复用于同一目标地址的所有服务;若需多 endpoint,用grpc.WithResolvers+ 自定义 resolver,而非多个 conn - 切忌在 handler 内 new client —— 即使加了 sync.Pool,也无法规避 TLS 初始化开销
避免在关键路径做 JSON 序列化/反序列化
json.Marshal 和 json.Unmarshal 在高并发下 CPU 消耗大、内存分配频繁,且默认不支持复用 bytes.Buffer,容易触发 GC 压力,间接拉高延迟毛刺。
替代方案要按场景选:
- 内部服务通信优先用 Protocol Buffers:gRPC 默认使用,序列化快、体积小、有强 schema 约束
- 若必须用 JSON,改用
easyjson或ffjson生成静态 marshaler,性能通常是标准库的 3~5 倍 - 对简单结构体,手写
MarshalJSON方法并复用bytes.Buffer,能减少 30%+ 分配次数
检查 net/http.ServeMux 和中间件链路深度
看似无关的路由和中间件,实际会叠加执行时间。一个 5 层中间件 + 正则匹配的 ServeMux,在 QPS 上万时可能贡献 0.5ms 以上固定开销。
优化重点不在“删中间件”,而在“让匹配更快”:
- 用
http.ServeMux时,路径必须以/结尾才支持子路径自动匹配;否则退化为 O(n) 全量遍历 - 高频接口考虑用
gorilla/mux或原生http.ServeMux+http.StripPrefix避免正则 - 日志、metric、auth 等中间件尽量前置判断短路(如 auth 失败立即 return),不要等走到 handler 才 panic 或 error
延迟优化不是堆参数或换框架,而是逐层确认每个环节是否真正必要——比如一个本可异步通知的审计日志,放在主调用链里就可能把 P99 推高 20ms。真实生产环境里,最常被忽略的是 DNS 缓存失效和 TLS session 复用失败,这两点查起来费时,但收益最大。











