应使用 httptest.NewServer 搭配 testing.Benchmark 测接口调用,避免直接发真实 HTTP 请求;只压测客户端逻辑(如序列化、重试策略),必要时用 hey 或 vegeta 做端到端测试。

用 testing.Benchmark 测接口调用,但别直接测 HTTP 请求
Go 的 Benchmark 函数本身不处理网络延迟抖动,直接在 Benchmark 里发真实 HTTP 请求会导致结果不可靠——每次运行受网络、服务端负载、DNS 解析等干扰,benchstat 也难比出有效差异。
- 真实接口调用应拆成「客户端构造」+「请求发送」两层,只对可复现的纯逻辑部分压测(如序列化、重试策略、header 构造)
- 若必须测端到端,改用专用工具:比如
hey -n 1000 -c 50 http://localhost:8080/api或vegeta attack -targets=targets.txt -rate=100 -duration=30s - 本地模拟服务用
httptest.NewServer,确保每次 Benchmark 走同一内存服务,排除网络变量
httptest.NewServer 搭配 Benchmark 的标准写法
这是最可控的接口调用性能测试路径:用 httptest 启一个固定响应的服务,再让 client 轮询它。所有耗时都反映 client 侧行为(编码、transport 复用、错误处理等)。
func BenchmarkHTTPClientCall(b *testing.B) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte(`{"id":1,"name":"test"}`))
}))
defer srv.Close()
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
req, _ := http.NewRequest("GET", srv.URL+"/api", nil)
resp, err := client.Do(req)
if err != nil {
b.Fatal(err)
}
io.ReadAll(resp.Body)
resp.Body.Close()
}}
- 务必调用
b.ResetTimer(),把httptest.NewServer启动和 client 初始化时间剔除 -
io.ReadAll(resp.Body)必须执行,否则连接可能被复用失败或触发 idle timeout - 不要在循环里重复 new
http.Client或http.Request—— 这会掩盖真实瓶颈
测 JSON 编解码开销:绕过网络,直击 json.Marshal/json.Unmarshal
很多“接口慢”实际卡在 payload 处理。用 Benchmark 单独压测编解码,能快速定位是否该换 easyjson 或 msgpack。
立即学习“go语言免费学习笔记(深入)”;
var payload = struct {
ID int `json:"id"`
Name string `json:"name"`
}{ID: 123, Name: "benchmark-test"}
func BenchmarkJSONMarshal(b *testing.B) {
for i := 0; i < b.N; i++ {
, = json.Marshal(payload)
}
}
func BenchmarkJSONUnmarshal(b *testing.B) {
data, _ := json.Marshal(payload)
b.ResetTimer()
for i := 0; i < b.N; i++ {
var v struct{ ID int; Name string }
json.Unmarshal(data, &v)
}
}
- 提前
json.Marshal一次生成data,避免在循环里重复编码干扰解码测试 - 结构体字段名保持小写 + tag,和线上真实 payload 一致,否则 benchmark 和实际表现偏差大
- 如果
Unmarshal耗时 > 100ns/op,且字段多、嵌套深,值得引入easyjson生成静态方法
容易被忽略的 transport 配置影响
默认 http.DefaultClient 的 Transport 是共享的,Benchmark 多次运行会复用连接池,但首次请求仍要建连。如果你测的是“冷启动首请求”,得显式关掉 keep-alive:
- 加
req.Header.Set("Connection", "close")强制每次新建 TCP 连接 - 设
Transport.MaxIdleConns = 0禁用空闲连接复用 - 注意
Timeout设置过短会导致大量context deadline exceeded错误,压测时建议设为30 * time.Second以上
真正上线前,transport 参数必须和生产环境一致,否则 benchmark 结论毫无意义。











