http/1.1 keep-alive 默认启用但需双方协商,服务端须显式配置idletimeout等参数并配合nginx等中间件正确设置keepalive_timeout和proxy_http_version 1.1才能生效。

HTTP/1.1 的 Keep-Alive 不是默认“开箱即用”的
很多人以为只要用了 http.Server,长连接就自动生效了——其实不是。Go 的 HTTP server 默认启用 Keep-Alive,但客户端是否复用连接,取决于双方协商结果;而服务端的空闲连接若不显式管理,会堆积并最终被内核或中间件(如 Nginx)主动断开。
关键参数必须手动设: IdleTimeout 控制连接空闲多久后关闭,ReadTimeout/WriteTimeout 防止慢请求霸占连接,MaxHeaderBytes 避免恶意大头导致内存暴涨。
-
IdleTimeout建议设为 30–60 秒,太短会导致客户端频繁重连,太长则积压大量 idle fd - 不要只设
WriteTimeout而忽略ReadTimeout:HTTP 请求头读取卡住时,连接就永远 hang 在那里 - 若前端有 Nginx,确保它也配了
keepalive_timeout和proxy_http_version 1.1,否则 Go 端再怎么调也没用
HTTP/2 多路复用比调参更直接有效
HTTP/1.1 的 Keep-Alive 是“串行复用”:一个连接同一时间只能处理一个请求(除非 pipelining,但基本没人用)。HTTP/2 才是真正的并发复用——单连接上可同时发多个 request,响应乱序返回也不影响。
Go 1.6+ 原生支持 HTTP/2,但有个硬性前提:http.Server 必须使用 TLS 启动,且证书合法(自签名需客户端显式信任)。纯 HTTP 端口(如 :8080)无法协商 HTTP/2。
- 启动时用
http.ListenAndServeTLS,而非ListenAndServe - 即使本地开发,也建议用
mkcert生成本地可信证书,避免因 ALPN 协商失败回退到 HTTP/1.1 - 检查是否生效:用
curl -v --http2 https://localhost:8443,看到Using HTTP/2和ALPN, offering h2才算成功
连接数暴涨时,SO_REUSEPORT 比协程池更底层有效
当并发连接突破万级,你会发现 CPU 并没跑满,但 accept() 系统调用成为瓶颈——所有连接都挤在同一个 socket listen queue 上,单个 Go runtime 线程争抢 accept 锁。
Linux 3.9+ 支持 SO_REUSEPORT,允许同一端口绑定多个 listener,内核按 hash 分流连接。Go 1.11+ 通过 net.ListenConfig{Control: ...} 可启用,无需改业务逻辑。
- 必须用
net.ListenConfig+syscall.SetsockoptInt32显式开启,标准http.ListenAndServe不支持 - 搭配
runtime.GOMAXPROCS设为 CPU 核心数,才能让每个 listener 绑定到独立 OS 线程 - 注意:Docker/K8s 中若未配置
hostNetwork: true或 hostPort,可能因 namespace 隔离导致复用失效
别在 handler 里直接起 goroutine 而不控速
有人以为“Go 协程轻量,随便 go 就行”,结果 QPS 上去后,goroutine 数爆炸,调度器过载,GC 频繁停顿,反而吞吐下降。
连接复用的前提是请求能快速完成。如果每个请求都 spawn 一堆无约束 goroutine 去查 DB、调下游,那连接再复用也没意义——连接空闲着,后端却在排队。
- 用带缓冲的 channel 或
ants这类协程池限制并发 worker 数,比如ants.NewPool(1000) - 对下游调用务必加
context.WithTimeout,避免一个慢依赖拖垮整条连接生命周期 - 监控
runtime.NumGoroutine()和net.Conn.LocalAddr()的 fd 数,两者持续增长不同步,说明 goroutine 泄漏了











