Go的http.Server无一键清理僵尸连接功能,必须通过显式配置ReadTimeout、WriteTimeout、IdleTimeout(最关键,如30秒)和ReadHeaderTimeout来预防连接卡住;已卡死连接只能靠Shutdown优雅终止或重启。

Go 的 http.Server 本身不支持“一键清理僵尸连接”
Go 标准库的 http.Server 没有提供类似 CloseIdleConns() 这样的全局清理接口。所谓“十万僵尸空闲长连接”,通常是客户端断开后,服务端 TCP 连接未及时回收(处于 TIME_WAIT 或半关闭状态),或 Keep-Alive 连接被客户端异常丢弃但服务端还在等读——这时连接卡在 read 系统调用上,表现为 goroutine 泄漏 + 文件描述符堆积。
根本原因不是连接“多”,而是连接“卡住没退出”。所以重点不在“清理”,而在“不让它卡住”。
http.Server 必须显式配置超时字段才能控制空闲连接生命周期
默认情况下,http.Server 的 ReadTimeout、WriteTimeout、IdleTimeout 全为 0,意味着不限制——这正是十万空闲连接的温床。
关键配置项(必须全部设):
立即学习“go语言免费学习笔记(深入)”;
-
ReadTimeout:限制从连接建立到读完请求头/体的总时间(防慢速攻击) -
WriteTimeout:限制写响应的总耗时(防后端阻塞导致连接挂起) -
IdleTimeout:限制连接空闲(无读写)的最大持续时间(这才是治“僵尸”的核心) -
ReadHeaderTimeout:建议也设,防止请求头迟迟不来卡死 accept 后的 goroutine
示例:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
srv := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 30 * time.Second, // ⚠️ 这个最关键,30 秒无活动就关连接
ReadHeaderTimeout: 2 * time.Second,
}已有卡住的连接无法“批量 kill”,只能靠重启或信号触发优雅关闭
一旦连接已卡在 read 上(比如客户端拔网线但没发 FIN),Go runtime 不会主动中断该系统调用——没有类似 Linux ss -K 那种强制踢掉 socket 的机制。
可行手段只有两个:
- 调用
srv.Shutdown(context.WithTimeout(...)):触发优雅关闭,等待活跃请求结束,并拒绝新连接;但**已卡住的连接仍会等到IdleTimeout到期才释放**(不会提前中断) - 发送
SIGHUP或SIGUSR2(需自己注册 signal handler)+ 调用Shutdown:本质还是走优雅路径,不能秒杀
注意:srv.Close() 是粗暴关闭,会立即断开所有连接(包括正在处理的),可能导致响应截断或客户端报 connection reset,不推荐用于线上。
真正压测/运维中要盯住的三个指标
“十万空闲连接”往往是症状,背后是配置缺失或客户端行为异常。上线前必须确认:
- 用
netstat -an | grep :8080 | grep ESTABLISHED | wc -l或ss -tn state established '( dport = :8080 )' | wc -l看真实 ESTABLISHED 数,别只信lsof -i :8080 | wc -l(它包含 TIME_WAIT) - 检查
/proc/<pid>/fd/</pid>下文件描述符数量是否逼近ulimit -n,卡住连接会持续占 fd - 用
go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2看是否有大量 goroutine 停在runtime.netpoll或internal/poll.runtime_pollWait—— 这就是卡在 read 的证据
超时参数设得再严,如果客户端疯狂建连又不发数据(比如 keep-alive 但永远不发下一个 request),IdleTimeout 就是唯一防线。别指望事后“一键清理”,得靠事前堵住入口和设好自动熔断点。

















