Nginx转发WebSocket断连因未透传Upgrade和Connection头,需配置proxy_set_header Upgrade $http_upgrade、Connection "upgrade"、http_version 1.1、proxy_buffering off及长超时;Go多实例共享状态须用Redis Pub/Sub或Hash,禁用数据库存连接;会话粘性推荐基于cookie或参数哈希;Go中Read/WriteMessage须设超时、用PongHandler防阻塞。
为什么直接用 Nginx 转发 WebSocket 会断连
因为 WebSocket 升级请求(
)需要透传,而默认 Nginx 配置会缓存、重写或丢弃
和
头。不显式放行,后端 Go 服务收不到完整握手,连接在 101 切换阶段就失败。
实操建议:
在 Nginx 的
块中必须添加:
禁用
,避免 WebSocket 帧被缓冲延迟:
设置合理超时,防止长连接被中间设备(如云厂商 LB)静默断开:
Go 后端如何支持多实例共享 WebSocket 连接状态
单机内存无法跨进程同步
对象,负载均衡下用户可能被调度到不同实例。若业务需广播、房间管理或状态查询,必须把连接元数据外移到共享存储。
常见方案与取舍:
立即学习
“
go语言免费学习笔记(深入)
”;
用 Redis + Pub/Sub 实现消息广播:每个 Go 实例订阅同一 channel,收到消息后只向本机已连接的客户端推送;
需手动处理重连和消息丢失
用 Redis Hash 存连接归属(
),配合分布式锁做连接注册/注销;注意
和过期时间必须原子执行
避免用数据库存实时连接状态——写频次高、延迟不可控,
在万级并发下易成瓶颈
如何让同一个用户的 WebSocket 请求总落在同一台 Go 实例上
不是必须,但能简化状态管理。Nginx 默认轮询不保证粘性,需显式配置。
可行做法:
基于请求头做 IP 哈希:
但 NAT 环境下大量用户共用一个出口 IP,会导致严重倾斜
更可靠的是基于 Cookie 或 URL 参数哈希:
前提是前端在首次建连时主动带上唯一标识(如登录后的
)
如果用云厂商 SLB(如阿里云 ALB),可开启「会话保持」并指定 Cookie 名称,比 Nginx 更稳定
Go 代码里哪些地方容易触发隐式阻塞导致连接堆积
WebSocket 是长连接,
和
都是阻塞调用。一个 goroutine 卡住,整个连接就卡死,连接数涨上去后新请求排队,最终触发超时或 OOM。
关键避坑点:
绝不在
循环里做同步 HTTP 调用或数据库查询;应发消息到 worker channel,由独立 goroutine 处理
写操作必须加超时控制:
否则对端断连未及时探测,本机会持续尝试发送,goroutine 泄露
用
替代手动读
,避免漏处理导致连接被误判为失效
不要用
做心跳超时,它不随连接关闭自动清理;改用
配合
handler 更轻量
真实压测中,90% 的“连接数暴涨”问题都出在没设写超时或 Pong 处理缺失。这些细节不报错,但会让系统在流量高峰时无声崩溃。
Upgrade: websocketConnectionUpgradelocationproxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;proxy_bufferingproxy_buffering off;proxy_read_timeout 86400;
proxy_send_timeout 86400;connredis.PubSubuser_id → instance_id:portSETNXINSERT/DELETEupstream ws_backend {
ip_hash;
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}hash $cookie_ws_route consistent;
hash $arg_sid consistent;sidconn.ReadMessage()conn.WriteMessage()ReadMessageconn.SetWriteDeadline(time.Now().Add(10 * time.Second))conn.SetPongHandler()Pingtime.Afterconn.SetReadDeadline()pong