必须显式设置IdleTimeout,否则空闲连接会耗尽文件描述符和goroutine;验证方式是手动建立连接后等待超时是否自动断开,并配合netstat、goroutine数和/proc/pid/fd等指标实时监控。

用 wrk 压测前先确认 Go HTTP Server 的 IdleTimeout 是否生效
不设 IdleTimeout 的 Go 服务在突发流量下极易被长连接拖垮,哪怕你限流逻辑写得再严密——恶意客户端可维持上千个空闲连接,把文件描述符和 goroutine 耗尽。这不是代码 bug,是配置缺失。
验证方式很简单:启动服务后,用 netstat -an | grep :8080 | grep ESTABLISHED | wc -l 查连接数;再开一个 telnet 或 nc 手动连上但不发请求,等 30 秒看是否自动断开。如果没断,说明 IdleTimeout 没生效。
-
ReadTimeout和WriteTimeout只管单次读写,对空闲连接完全无效 - Go 1.8+ 才有
IdleTimeout字段,老版本需升级或换 fasthttp 等替代库 - 若用反向代理(如 Nginx),必须同步配
proxy_read_timeout和proxy_send_timeout,否则它会提前断连,掩盖 Go 层问题
wrk 脚本要模拟真实攻击模式,不能只跑平均 QPS
单纯 wrk -t4 -c100 -d30s http://localhost:8080/api 测不出问题。真实突发流量是“短时高并发 + 长连接 + 混合路径”,比如 1 秒内打 500 个登录请求,其中 30% 是重复 IP、20% 带非法 header、10% 故意不读响应体。
推荐写 Lua 脚本控制行为,例如:
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
wrk.method = "POST"
wrk.body = '{"user":"test","pwd":"123"}'
wrk.headers["X-Forwarded-For"] = function() return math.random(1,255) .. "." .. math.random(0,255) .. ".0.1" end
wrk.timeout = 2
<p>function init(args)
requests = 0
end</p><p>function request()
requests = requests + 1
if requests % 7 == 0 then
-- 每 7 次故意不读响应,制造空闲连接
return nil
end
return wrk.request()
end- 不读响应体(
return nil)会让 Go 连接卡在WriteHeader后,暴露IdleTimeout是否起作用 - 动态生成
X-Forwarded-For可绕过简单 IP 限流,验证中间件是否真按真实 IP 提取 - 务必加
wrk.timeout = 2,否则超时请求会堆积,干扰结果判断
压测中盯死三个指标,别只看成功率
成功率 99% 不代表稳——可能 1% 请求耗时 10 秒,已拖垮下游。真正关键的是:
-
runtime.NumGoroutine():持续上涨说明连接未释放或 handler 卡住,检查是否有 goroutine 泄漏 -
runtime.ReadMemStats().Mallocs:每秒分配对象数突增 5 倍以上,大概率是没复用bytes.Buffer或没预热sync.Pool - 系统级
cat /proc/<pid>/fd | wc -l</pid>:接近 ulimit -n(如 65535)就危险,说明连接泄漏或IdleTimeout失效
这些值必须在压测过程中实时打印,而不是等结束再查日志。可用 pprof 的 /debug/pprof/goroutine?debug=2 快速 dump 当前 goroutine 栈。
限流器初始化必须避开热路径,否则压测时自己先崩
看到有人在每个 handler 里写 limiter := rate.NewLimiter(10, 5),这在 wrk 并发 1000 时会瞬间创建上千个限流器,内存暴涨且 GC 压力翻倍——不是限流没效果,是限流器本身成了瓶颈。
正确做法是:
- 全局复用一个
rate.Limiter实例做全局限流 - 按 IP 或用户 ID 分片时,用
sync.Map缓存,且 key 必须标准化(IPv6 地址要去掉::ffff:前缀) -
sync.Map.LoadOrStore里不要调用带副作用的函数,比如log.Println或time.Now(),它们会成为性能热点
最容易被忽略的一点:如果你用了 Redis 分布式限流,压测脚本里不能直接连本地 Redis —— 它会成为单点瓶颈。要么用集群模式,要么在 wrk 客户端侧 mock 限流响应,聚焦验证 Go 服务自身稳定性。

















