
haproxy默认无法对http/1.1持久连接进行请求级负载均衡,导致客户端长期复用同一后端连接;根本原因在于keep-alive机制使tcp连接长期存活,而haproxy需在连接粒度或请求粒度上控制转发行为。
在Go客户端与服务端(如基于go-martini)长期运行的场景中,若未显式关闭HTTP连接,客户端会持续复用同一个TCP连接发送后续请求。此时HAProxy虽接收到多个HTTP请求,但因底层TCP连接未断开,其默认的http-keep-alive模式会将所有请求复用至同一后端服务器,造成“会话粘滞”(session stickiness),而非预期的轮询或随机负载均衡——这正是你观察到netstat中存在长期存活连接、且curl能正常轮询(因其短连接特性)而自研Go程序却无法均衡的根本原因。
✅ 推荐解决方案:在HAProxy配置中启用 http-server-close
这是最优雅、无需修改业务代码的方案。它让HAProxy在每个HTTP请求结束后主动关闭与后端服务器的连接,但保持与客户端的连接(支持Keep-Alive),从而实现真正的请求级负载均衡:
backend web_servers
mode http
balance roundrobin
http-server-close # ← 关键配置:断开后端连接,保留客户端连接
server srv1 10.0.1.10:8080 check
server srv2 10.0.1.11:8080 check✅ 优势:客户端仍可享受连接复用(降低延迟),HAProxy每次请求都可重新选择后端,完全符合负载均衡语义。
⚙️ 其他可行方案(按优先级排序)
http-close(激进):强制关闭客户端和后端两端的连接,等效于HTTP/1.0行为。适用于调试或兼容性要求极高的场景,但牺牲了客户端连接复用收益。
-
Go客户端侧控制:在http.Client中禁用Keep-Alive:
client := &http.Client{ Transport: &http.Transport{ DisableKeepAlives: true, // 强制每请求新建TCP连接 }, }或对单个请求设置:
req, _ := http.NewRequest("GET", "http://haproxy/app", nil) req.Close = true // 等效于添加 Connection: close 头 Go服务端侧控制(不推荐):在martini或标准http.ResponseWriter中写入Connection: close头。但此方式依赖客户端遵守,且无法解决HAProxy连接复用逻辑,效果不可靠。
? 验证与注意事项
- 启用http-server-close后,可通过haproxy -vv确认版本支持(v1.5+完全支持),并在日志中观察srv1/srv2交替出现;
- 确保后端服务无自身连接池或长连接缓存(如某些HTTP库默认复用连接),避免干扰HAProxy行为;
- 若使用HTTPS后端(https://),http-server-close依然生效,但需注意TLS握手开销略有上升——通常可接受;
- 切勿仅依赖Connection: close响应头:HAProxy默认不解析或响应此头来决定连接策略,必须通过显式指令(如http-server-close)控制。
综上,问题根源在于HTTP/1.1持久连接与HAProxy默认连接复用模型的冲突,而非客户端或服务端bug。采用http-server-close是生产环境最稳定、低侵入性的解法,兼顾性能与均衡性。










