直接用 http.get 会导致连接池失效、time_wait 爆炸、dns 查询重复、tls 握手频繁、端口耗尽和 too many open files 错误;必须复用 http.client 并正确配置 maxidleconns、maxidleconnsperhost、idleconntimeout、tlshandshaketimeout 四个参数。

压测脚本里直接用 http.Get 会出什么问题
它每次调用都新建一个 http.Client,而默认 Transport 的 MaxIdleConns 是 0,等于禁用连接池。后果很实在:
-
TIME_WAIT连接爆炸,本机端口快速耗尽 - 同一域名反复走 DNS 查询,延迟毛刺明显
- HTTPS 请求频繁 TLS 握手,CPU 和 RT 都被拖垮
- 并发稍大就卡在
socket: too many open files,根本不是服务瓶颈,是你本机ulimit限制了
必须显式复用 http.Client,并配齐四个关键参数:MaxIdleConns、MaxIdleConnsPerHost、IdleConnTimeout、TLSHandshakeTimeout。缺一不可,且前两者必须设为相同值(比如都设 200)。
怎么写一个不泄露 goroutine 的压测循环
裸写 for i := 0; i 等于瞬间拉起 n 个 goroutine,这不是“100 QPS”,是“100 并发瞬发”,测的是你本机网络栈极限,不是目标服务真实吞吐。
- 用
sync.WaitGroup等待所有 worker 结束,避免主进程提前退出 - 用带缓冲的
chan struct{}做并发控制(信号量),别靠time.Sleep控节奏 - 每个请求必须带
context.WithTimeout,否则失败请求会无限 hang 住 goroutine - HTTP 响应体必须显式
resp.Body.Close(),否则连接无法释放回池
pprof 监控哪些指标才真正有用
压测时盯着 top 看 CPU 是最低效的方式。pprof 能暴露真正影响吞吐的关键信号:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
-
/debug/pprof/goroutine?debug=2:看是否有 goroutine 卡死或持续增长(泄露迹象) -
/debug/pprof/heap:确认内存是否随请求线性增长(没复用对象 or 没释放 buffer) -
/debug/pprof/block:查阻塞点,比如锁竞争、channel 阻塞、DNS 解析卡住 -
/debug/pprof/mutex:高并发下锁争用是否成为瓶颈
注意:pprof 默认只监听 localhost,部署到服务器后若没改 http.ListenAndServe("localhost:6060", nil) 为 ":6060",外部就访问不到;另外 /goroutine?debug=2 输出完整栈,别在高并发时频繁刷,会拖慢服务。
为什么本地压测结果经常失真
常见错误是压测前没关掉 Go 的调试日志(比如 log.Println),导致 I/O 成瓶颈,压出来的 QPS 偏低,误判服务性能。
- Go Web 服务启动前,确保关闭所有非必要日志输出(尤其
log包的默认输出) - 命令行工具推荐
hey而非ab:前者支持 HTTP/2、连接复用、更贴近真实客户端行为 - 用
hey -n 1000 -c 50 http://localhost:8080/api/users发 1000 次请求,最大并发 50 - 加
-m POST -d '{"id":1}' -H "Content-Type: application/json"测试带 body 的接口 - 加
-h2强制走 HTTP/2(前提是你的 Go 服务启用了 TLS 或明确配置了 HTTP/2 支持)
最易被忽略的是:压测脚本和被压服务共用一台机器时,CPU、网络、文件描述符都是共享资源,结果反映的是整机瓶颈,而非服务本身。真要验极限,得把压测机和服务机物理隔离。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










