必须手动构造禁用复用的http.Client并用time.Ticker控频:设MaxIdleConns=0、MaxIdleConnsPerHost=0、IdleConnTimeout=0、ForceAttemptHTTP2=false,配合chan+worker实现稳定QPS。

压测结果失真,90% 是因为没管 http.Client 和 http.Transport 的默认行为——它根本不是为压测设计的。
为什么不能直接用 http.Get 做压测
每次调用 http.Get 都会新建一个 http.Client,而它的默认 Transport:MaxIdleConns = 0(禁用连接池),IdleConnTimeout = 0(不复用),但 DNS 缓存、TLS 会话仍可能被复用,导致结果既不稳定又不可比。
- 现象:瞬间并发打出
too many open files、dial tcp: lookup xxx: no such host、延迟毛刺剧烈 - 本质:你测的不是服务吞吐,是你本机网络栈和 Go 调度器的崩溃点
- 后果:本地跑出 5000 QPS,上线后真实用户连 1000 都扛不住
真实接口压测必须禁用连接复用
要模拟“新用户首次访问”,就得手动构造一个**禁用所有复用机制**的 http.Client,否则压测掩盖真实瓶颈。
MaxIdleConns = 0MaxIdleConnsPerHost = 0IdleConnTimeout = 0ForceAttemptHTTP2 = false- HTTPS 场景下加
TLSClientConfig = &tls.Config{InsecureSkipVerify: true}(仅测试环境)
这四条缺一不可。设了前两个但没设 IdleConnTimeout,空闲连接不会及时关闭,照样堆积;不关 HTTP/2,某些服务端会因协商失败静默降级或超时。
立即学习“go语言免费学习笔记(深入)”;
怎么控稳 QPS 而不是瞬发并发
写 for i := 0; i 不是压测,是 DoS。真实用户按固定节奏发请求,比如 100 QPS,意味着每 10ms 发一个,不是同时发 100 个再等 1 秒。
- 用
time.Ticker控制请求间隔,例如ticker := time.NewTicker(10 * time.Millisecond) - 配合
sync.WaitGroup等待全部完成,避免 goroutine 泄漏 - 别只靠
semaphore限并发数——它控峰值,不控速率;QPS 不稳时优先查 ticker 是否被阻塞或 GC 干扰
示例节奏控制片段:
for i := 0; i < totalRequests; i++ {
<-ticker.C
wg.Add(1)
go func() {
defer wg.Done()
resp, _ := client.Do(req)
// 记录耗时、状态码
resp.Body.Close()
}()
}压测报告里真正该盯的指标
终端里那个 “Requests/sec” 数字最容易骗人。它掩盖长尾、忽略错误、不反映服务稳定性。
- P95/P99 延迟比平均值重要十倍——P95 升高说明部分请求已开始排队或卡在锁、DB 连接池、GC STW 上
- 非 2xx 状态码计数必须单独统计,尤其是 429(限流)、503(服务不可用)、0(连接超时)
- DNS/Connect/Processing 分段耗时(
hey或vegeta输出里有)能快速定位是客户端问题(DNS 慢)、网络问题(Connect 高)、还是服务端逻辑慢(Processing 高)
最容易被忽略的是:压测机和被测服务是否同机。localhost 压测绕过部分 TCP 栈,结果偏乐观;而 DNS 解析、TLS 握手、TIME_WAIT 处理这些真实链路环节,只有跨机器才能暴露出来。



















