nginx默认轮询导致websocket连接“乱跳”,因各go实例不共享连接状态,重连分发至不同后端将丢失房间、心跳及登录上下文;必须通过ip_hash、consistent hash或云slb会话保持实现连接绑定,并配合redis pub/sub外置状态与广播。

为什么 Nginx 默认轮询会让 WebSocket 连接“乱跳”
Go 的 net/http 或 gorilla/websocket 实例本身不共享内存连接对象,每个进程只维护自己收进来的 *websocket.Conn。一旦客户端重连被 Nginx 分到另一台机器,之前建立的房间、心跳状态、用户登录上下文全丢失——表现为消息收不到、重复登录、广播失效。
这不是 Go 代码写错了,是负载均衡层没做会话绑定。Nginx 默认 round-robin 对 WebSocket 是灾难性的,必须显式干预。
-
ip_hash最简但有严重缺陷:NAT 环境下几十万用户共用一个出口 IP,全部压到同一台后端,极易打满 - 前端若已带登录态
sid或user_id,优先用hash $arg_sid consistent或hash $cookie_ws_route consistent - 云厂商 SLB(如阿里云 ALB)开启「会话保持」并指定 Cookie 名,比自建 Nginx 更稳,不用操心重连时 Cookie 覆盖逻辑
proxy_set_header Connection "upgrade" 不能写成 $http_connection
WebSocket 握手依赖两个关键头:Upgrade: websocket 和 Connection: upgrade。Nginx 默认会把 Connection: keep-alive 透传给后端,而 Go 服务只认字面值为 upgrade 的 Connection 头才触发 101 切换。
如果写成 proxy_set_header Connection $http_connection,当客户端发来的是 keep-alive,后端就收不到 upgrade,握手失败,返回 200 而非 101,连接静默降级为 HTTP。
- 必须写死
proxy_set_header Connection "upgrade" - 更健壮的做法是加
map预处理:map $http_upgrade $connection_upgrade { default upgrade; '' close; },再在 location 中用proxy_set_header Connection $connection_upgrade -
proxy_http_version 1.1必须启用,HTTP/1.0 不支持 Upgrade 机制
proxy_read_timeout 设太小会导致“空闲秒断”
Nginx 默认 proxy_read_timeout 是 60 秒。WebSocket 连接空闲时没有数据帧,只要超过这个时间没读到新内容,Nginx 就直接关 TCP 连接,Go 后端完全无感知,客户端收到 onclose 事件却查不到错误码。
这不是网络抖动,是 Nginx 主动断链。尤其在金融行情、IM 群聊等低频推送场景,用户挂后台几分钟,回来就掉线。
- 设为
proxy_read_timeout 86400(24 小时),且确保 Go 服务端SetReadDeadline也设得比它长(比如 25 小时),避免竞态 -
proxy_send_timeout同样要设长,否则大文件二进制帧分片发送中途超时,连接中断 -
proxy_buffering off必须关闭,否则 WebSocket 帧被缓存,延迟不可控,pong 响应卡住导致心跳失败
Redis Pub/Sub 是跨实例广播的唯一可行方案
别用数据库存连接状态。每条 INSERT INTO ws_conn (user_id, conn_id, instance) 在万级并发下就是写瓶颈;DELETE 清理还要加分布式锁防重复删,复杂度爆炸。
真正能跑得起来的只有 Redis:
- 每个 Go 实例起一个
redis.PubSub订阅公共 channel(如ws:room:1001),收到消息后只推给本机持有的对应*websocket.Conn - 连接注册用
HSET ws:conn:user_id instance_id:port+EXPIRE,原子性靠SETEX或 Lua 脚本保证 - 务必实现
PubSub重连逻辑:订阅断开后需重新SUBSCRIBE,否则漏消息;Redis 故障期间消息直接丢,业务层要有补偿(如拉取离线消息)
会话保持只是起点,连接状态外置和广播通路才是多实例 WebSocket 的真正门槛。配置写错顶多连不上,状态设计错会导致消息飞丢、用户看到“别人发的消息自己收不到”。











