Go微服务压测不能只靠go test -bench,因其仅测单函数性能,无法模拟网关→A→B→C→DB全链路、header透传、JWT、gRPC metadata、超时级联等真实流量特征,更无法验证熔断、日志爆炸、连接池耗尽等线上问题。

Go 微服务压测为什么不能只靠 go test -bench
因为 go test -bench 测的是单函数吞吐和延迟,而全链路压测要模拟真实用户请求经过网关、服务 A → B → C → DB 的完整路径。流量特征(如 header 透传、JWT 签名、gRPC metadata、超时级联)一概丢失,压出来的 QPS 和线上毛关系没有。
真正要验证的,是服务在真实流量压力下的熔断行为、日志爆炸、连接池耗尽、trace 丢帧这些现象。所以必须从入口层捕获真实流量,再可控回放。
- 线上流量自带业务语义(比如
X-Request-ID、X-Trace-ID),直接录制能保留上下文,避免伪造数据引发鉴权/幂等逻辑误判 - HTTP/gRPC 混合调用的服务链路,用
ab或hey只能打到第一层,没法驱动下游服务真实参与 - 录制时若不剥离敏感字段(如
Authorization: Bearer xxx、手机号),回放会污染生产环境或触发风控
用 go-micro 或 kitex 做流量录制要注意什么
不是所有 Go RPC 框架都支持无侵入录制。比如原生 net/rpc 没法 hook 请求体,而 kitex 提供了 GenericInvoke + MW(中间件)机制,go-micro v3 则依赖 BeforeRequest 钩子 —— 但这两个钩子默认不包含原始二进制 payload,容易漏掉 protobuf 序列化后的字节流。
- HTTP 录制推荐用
http.ServeMux包一层代理,把*http.Request的Body读出来存成 JSON(注意:读完要重置Body,否则下游 handler 拿不到数据) - gRPC 录制必须开启
grpc.WithUnaryInterceptor,且拦截器里调用grpc.MethodFromServerStream获取方法名,再用proto.Unmarshal解包 —— 否则录下来的只是加密后的[]byte,回放时无法反序列化 - Kitex 用户别直接用
kitex-gen生成的 client 调用,得走generic.Client,否则回放时类型不匹配会 panic
gorilla/handlers 录制 HTTP 流量的三个硬坑
很多人用 gorilla/handlers.CompressHandler 或 LoggingHandler 顺手加录制逻辑,结果发现录下来的 body 是空的、gzip 编码的,或者 timestamp 全是录制时刻而非原始请求时刻。
立即学习“go语言免费学习笔记(深入)”;
-
Request.Body是io.ReadCloser,读一次就 EOF,必须用io.TeeReader+bytes.Buffer双写:一份给下游 handler,一份存档 - 如果上游用了
Content-Encoding: gzip,录制前要先解压(gzip.NewReader(r.Body)),否则存下来的是压缩流,回放时服务端可能不识别 - 别信
time.Now()—— 录制时应从X-Forwarded-For或X-Real-IP提取原始客户端 IP,并用r.Header.Get("X-Request-Time")(如果有的话)替代本地时间,不然重放时 trace 时间线全乱
回放阶段如何避免“压垮自己又没压对”
回放不是把 JSON 文件循环发出去就行。curl -X POST 批量发只会让压测机 CPU 先爆,而目标服务还没怎么动。关键在控制并发模型和流量节奏。
- 用
golang.org/x/sync/errgroup控制 goroutine 并发数,别用for i := range data { go send(data[i]) }—— 这样会瞬间起几千 goroutine,调度器直接卡死 - 回放时必须重写
Host、Authorization、X-Trace-ID头,否则可能打到线上 trace 系统,或触发重复消费(尤其有消息队列环节时) - 如果原始流量含重试逻辑(比如 5xx 后客户端自动重发),回放时要主动丢弃重复
X-Request-ID,否则会放大流量倍数,你以为压了 100 QPS,实际打了 400
最麻烦的其实是时间膨胀:原始流量是 5 分钟内均匀进来的,你 1 秒内全发出去,下游缓存、DB 连接池、限流器根本扛不住。得按原始时间戳做差值 sleep,这个逻辑很容易被跳过。










