应使用 httptest.NewRecorder 直接测试 Handler 性能,避免网络层干扰;真实链路压测用 go-wrk 或 hey,同时监控 goroutine、内存、GC 等关键指标。

用 net/http/httptest 测试 Handler 性能,不是测真实网络
很多人一上来就用 http.Get 去压测自己写的 API,结果测的其实是本地 DNS、TCP 握手、TLS 开销甚至本机网络栈——根本没打到业务逻辑上。真正想验证 Handler 本身的吞吐和延迟,得绕过网络层,直接调用。用 httptest.NewServer 或 httptest.NewRecorder 是更干净的方式。
-
httptest.NewRecorder()适合单次请求性能分析:构造*http.Request,传给你的http.HandlerFunc,用httptest.NewRecorder()捕获响应,再测执行耗时 -
httptest.NewServer()适合模拟客户端行为(比如带重试、并发请求),但底层仍是内存 HTTP server,不走 socket - 注意别在测试里混用真实外网地址(如
http://api.example.com),那会把 DNS 解析、连接池复用、超时策略全卷进来,干扰判断
用 go-wrk 或 hey 做真实链路压测
要测端到端表现(比如部署后的真实 QPS、P95 延迟、连接堆积),必须走真实 TCP 连接。Go 生态里 go-wrk 更轻量、可嵌入测试代码;hey 功能更全、输出更直观,两者都支持 HTTP/1.1 和 HTTP/2。
-
go-wrk -n 10000 -c 100 http://localhost:8080/api/users:发 1 万请求,100 并发,结果含平均延迟、各分位延迟、错误数 - 压测前确保服务已预热(先跑几百请求让 GC、JIT、连接池稳定),否则首秒数据偏差极大
- 注意
hey默认启用 keep-alive,而某些 client 库(如resty)默认关闭,行为不一致会导致连接数暴涨,触发too many open files - 如果服务启用了 HTTPS,记得加
-insecure参数跳过证书校验,否则hey会卡在 TLS handshake
监控关键指标:不只是 QPS 和延迟
QPS 高不代表健康。Golang 网络服务容易在几个隐蔽点崩:goroutine 泄漏、内存持续增长、GC 频繁停顿、HTTP 连接未及时关闭。光看响应时间会漏掉这些。
- 用
runtime.ReadMemStats定期采样MemStats.Alloc和MemStats.TotalAlloc,看每秒分配量是否线性上涨 - 用
debug.ReadGCStats查 GC pause 时间占比,超过 5% 就值得警惕 - 用
net/http/pprof暴露/debug/pprof/goroutine?debug=2,压测中抓 goroutine dump,确认有没有卡在http.readLoop或自定义 channel 上 - 检查
http.Server的MaxConns、ReadTimeout、WriteTimeout是否设了合理值,否则连接堆积会拖垮整个进程
避免常见陷阱:超时、重试、连接池配置错位
很多“性能差”的问题其实来自 client 端配置反模式,而非 server 本身。
立即学习“go语言免费学习笔记(深入)”;
-
http.DefaultClient的Transport默认没有设置MaxIdleConns和MaxIdleConnsPerHost,压测时可能瞬间创建数千个 TCP 连接,触发EMFILE - 用
context.WithTimeout包裹请求时,超时时间必须大于服务端ReadTimeout+ 业务处理时间,否则 client 主动 cancel 会被记为失败,掩盖真实瓶颈 - 重试逻辑若没做指数退避(exponential backoff),加上服务端响应变慢,会引发雪崩式重试洪峰
- 别在 handler 里用
time.Sleep模拟耗时——它会阻塞 goroutine,而真实 I/O(如 DB 查询)是异步的,该用database/sql的上下文超时或io.Copy配合context控制











