Go百万级HTTP连接内存爆炸是因默认每个长连接独占goroutine(2KB栈+4KB缓冲区等),100万连接致内存超2–3GB、GC飙升;必须显式设Read/WriteTimeout(≤15s)和IdleTimeout(如60s)以避免资源泄漏。

Go 处理百万级 HTTP 连接时,内存爆炸不是配置问题,而是默认行为必然结果:每个长连接默认绑定一个 goroutine,每个 goroutine 至少 2KB 栈 + HTTP 缓冲区 + TLS 上下文,100 万连接轻松突破 2–3GB 内存,GC 频次飙升,runtime.ReadMemStats 中 HeapInuse 持续高位、Mallocs 线性上涨是典型信号。
为什么 net/http.Server 默认配置撑不住百万连接
标准 http.Server 是阻塞 I/O 模型,底层调用 conn.Read() 会挂起 goroutine 直到数据到达。这意味着:
- 每个活跃长连接(Keep-Alive)独占一个 goroutine,无法复用
-
ReadBufferSize/WriteBufferSize默认 4KB,100 万连接仅缓冲区就吃掉 ~8GB 虚拟内存(实际 RSS 取决于是否真正写满) - 未显式设置
IdleTimeout时,空闲连接无限期存活,FD 和内存持续累积 -
http.Request.Body是io.ReadCloser,但底层net.Conn生命周期由 server 控制,无法主动释放或复用
必须关闭的默认行为:Read/Write 超时与 IdleTimeout
不设超时不是“让连接更持久”,而是放任资源泄漏。关键参数必须显式覆盖:
-
ReadTimeout:防止慢速请求(如上传卡住)长期占用 goroutine,建议 ≤ 15s -
WriteTimeout:防止响应生成过慢或客户端接收阻塞,建议 ≤ 15s -
IdleTimeout:这是百万连接的生命线,必须设(如60 * time.Second),否则 ESTABLISHED 连接永不释放 -
MaxHeaderBytes:限制头部大小(如1 即 64KB),防恶意大 header 耗尽内存
漏掉其中任意一项,netstat -an | grep :PORT | wc -l 和 cat /proc/PID/status | grep VmRSS 就会告诉你真实代价。
立即学习“go语言免费学习笔记(深入)”;
sync.Pool 复用缓冲区与临时对象
高频分配的 buffer、decoder、encoder 是 GC 主力军。不用 sync.Pool,每秒万级请求就会触发数次 GC;用了,可降低 40–70% 的堆分配压力:
- 为
bytes.Buffer、json.Decoder、json.Encoder单独建池,New函数返回新实例 - 在 handler 入口
Get(),出口Put(),注意Put()前必须Reset() - 避免池中对象持有外部引用(如闭包、大 slice 底层 array),否则导致内存无法回收
示例:var jsonDecoderPool = sync.Pool{New: func() interface{} { return json.NewDecoder(nil) }},使用时 dec := jsonDecoderPool.Get().(*json.Decoder); dec.Reset(r.Body)。
绕过 http.Server 的连接接管方案
真要压到百万级,必须放弃 http.Serve 的封装,自己监听 net.Listener 并手动升级连接:
- 用
net.Listen("tcp", addr)获取 listener,再用ln.(*net.TCPListener).SetKeepAlive(30e9)启用 TCP keepalive - 循环
ln.Accept(),对每个net.Conn启动固定数量 worker(如runtime.NumCPU() * 2),而非 per-conn goroutine - worker 内部用
conn.SetReadDeadline()+ 非阻塞Read()模拟事件驱动,配合runtime.Gosched()让出调度权 - 连接断开时,必须显式
conn.Close(),否则 FD 泄露,ulimit -n很快 hitEMFILE
这个路径放弃了 HTTP 语义完整性(如自动 100-continue、header 解析),换来的是对连接生命周期的完全控制——这才是百万连接的底层前提。
最常被忽略的点:内存优化不是调几个 sync.Pool 就完事。它必须和连接生命周期管理、goroutine 数量硬限、系统级 ulimit 调整(fs.file-max、net.core.somaxconn)同步推进。少一环,压测时第一个崩的永远是内存或文件描述符。


















