全链路压测需在http handler第一行手动注入x-test-flag并透传至grpc、dao、redis等全链路,通过context动态路由影子库/缓存,严禁硬编码或依赖中间件自动解析。

全链路压测不是对单个接口跑 go test -bench,而是把真实用户请求从网关入口开始,完整驱动整个调用链:HTTP → 服务 A → gRPC 调用服务 B → Redis 查询 → MySQL 写入 → Kafka 发消息。所有中间件、超时控制、JWT 验签、trace 透传、goroutine 分发都必须参与其中,否则压出来的 QPS 和线上毫无关系。
X-Test-Flag 必须在 HTTP handler 第一行手动注入 context
Go 没有运行时 AOP,中间件里解析完 header 就调 next.ServeHTTP 是无效的——只要 handler 里起了 go func() 或调了 grpc.Dial,标识就断了。
- 入口必须写死这行:
if flag := r.Header.Get("X-Test-Flag"); flag == "1" { ctx = context.WithValue(r.Context(), testKey, true) } -
testKey不能是字符串,得是私有 struct 类型(如type testKey struct{}),避免 key 冲突 - Nginx 默认
large_client_header_buffers 4K,若同时传X-B3-TraceId、X-Scene-ID、X-Test-Flag,需调大,否则第一跳就丢标 - 别用
http.DefaultClient发下游请求——它不带 header,必须显式构造req.Header.Set("X-Test-Flag", "1")
gRPC metadata 透传必须手写 interceptor,RoundTrip 不生效
gRPC 的 metadata 不像 HTTP header 那样自动继承。服务端收不到 X-Test-Flag,90% 是因为 server interceptor 没从 metadata.FromIncomingContext(ctx) 提取,或 client interceptor 没用 metadata.AppendToOutgoingContext 回写。
- 客户端拦截器里不能只改
md["X-Test-Flag"] = []string{"1"},必须绑定到 context:ctx = metadata.AppendToOutgoingContext(ctx, "X-Test-Flag", "1") - streaming 场景下,每帧
SendMsg前都要重新塞一次 context,否则中间某次就丢失 - kitex/go-micro 用户注意:默认钩子不包含原始 protobuf payload,录下来的请求体是加密
[]byte,回放时反序列化失败
DAO 层动态路由影子库,硬编码 if testFlag 会崩
GORM 在 SQL 字符串里拼 "INSERT INTO stress_orders" 看似快,但事务、预编译、Session 缓存全失效——它根本不知道你要切库,仍按原表名生成语句,结果连 db.Create(&order) 都报错。
- 初始化 DAO 时传入两个
*sql.DB:一个prodDB,一个stressDB - 每次执行前查
ctx.Value(testKey),再选对应连接池调用Query/Exec,不碰 SQL 字符串 - Redis 同理:封装
redis.Client,Get方法里检查ctx,压测时自动把"user:1001"变成"stress_user_1001" - Elasticsearch/Kafka 也得隔离:
topic改为orders<em>stress</em>,索引名加stress前缀,消费者group id改为order-consumer-stress
流量复制本身不难,难的是上下文无损携带和下游行为可控。很多人录完流量一回放就写脏主库,不是工具问题,是没在每一层(HTTP、gRPC、DB、Redis、异步任务)统一从 context.Context 读标识做路由。漏掉任意一层,压测就变成事故演练。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











