压测必须复用http.client并调优transport四参数,否则因连接不复用、dns重复解析、tls握手堆积导致qps虚低及too many open files等错误;需设maxidleconns与maxidleconnsperhost相等(如200),idleconntimeout为30秒,tlshandshaketimeout为5~10秒,并配合time.ticker控频、ulimit调大系统文件数限制。

直接复用 http.Client 并显式调优 Transport,否则压测结果完全失真——http.Get 每次都新建 client,连接不复用、DNS 重复解析、TLS 握手堆积,QPS 虚低,还容易触发 dial tcp: lookup xxx: no such host 或 too many open files。
为什么不能直接用 http.Get 做压测
它底层每次调用都初始化一个全新 http.Client,而默认 Transport 的 MaxIdleConns 是 0(即禁用连接池),等于每请求都建新 TCP 连接。后果很实在:
-
TIME_WAIT连接爆炸,本机端口快速耗尽 - 同一域名反复走 DNS 查询,延迟毛刺明显
- HTTPS 请求频繁 TLS 握手,CPU 和 RT 都被拖垮
- 并发稍大就卡在
socket: too many open files,根本不是服务瓶颈,是你本机 ulimit 限制了
必须配置的 http.Transport 四个参数
这四个值不是“可配可不配”,而是压测能跑稳的前提。缺一不可,且有强依赖关系:
-
MaxIdleConns和MaxIdleConnsPerHost必须设为相同值(例如都设200),否则后者会被前者截断失效 -
IdleConnTimeout建议30 * time.Second:太短导致连接频繁重建,太长则空闲连接占资源 -
TLSHandshakeTimeout建议5 * time.Second~10 * time.Second:避免个别慢握手阻塞整个 worker goroutine
示例片段:
client := &http.Client{
Transport: &http.Transport{
MaxIdleConns: 200,
MaxIdleConnsPerHost: 200,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
},
}
别裸写 go req() 控制并发
写 for i := 0; i 等于瞬间拉起 n 个 goroutine,这不是“100 QPS”,是“100 并发瞬发”,测的是你本机网络栈和调度器极限,不是目标服务真实吞吐。
真实压测要控节奏,推荐用 time.Ticker + chan struct{} 实现稳定发压:
- 用
ticker := time.NewTicker(time.Second / time.Duration(tps))控制请求频率 - worker 数量 = 并发数,每个从
reqChan拿信号执行一次请求 -
chan struct{}零内存开销,比带数据的 channel 更轻量 -
totalRequests和tps分开传入,方便做阶梯压测(比如每 30 秒加 50 QPS)
Linux 下必须同步调大系统限制
Go 程序再怎么优化,ulimit -n 不调,压到 3000+ 并发就会卡死在 too many open files。这不是代码问题,是 OS 层面限制:
- 临时生效:
ulimit -n 65536 - 永久生效:修改
/etc/security/limits.conf,追加两行:* soft nofile 65536* hard nofile 65536 - 注意:改完需重新登录 shell 或重启用户 session 才生效
没调这个,前面所有 Transport 调优、goroutine 控制,全白搭。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











