WebSocket平滑迁移关键信号是SIGUSR2,需配合Redis共享状态、Nginx proxy_read_timeout调优(如设为90秒或86400)、前端预判式重连及token透传路由。

WebSocket连接平滑迁移的关键信号是SIGUSR2,不是重启
Golang服务扩容时最常犯的错误,就是用kill -9或简单systemctl restart——这会直接切断所有活跃连接。真正的平滑迁移依赖进程信号协作:SIGUSR2触发draining模式,即停止接受新连接,但允许已有连接自然结束或超时退出。
必须配合以下三点才有效:
- Upgrader设置
CheckOrigin为显式true或按需校验,否则握手阶段就被阻塞,draining无从谈起 - 所有连接状态(如用户ID、房间号)不能只存在内存里,得存到
Redis或类似共享存储,新进程启动后能立刻拉取上下文 - 消息确认机制要闭环:客户端发消息 → 服务端落库/入队 → 返回ack → 客户端收到ack才认为发送成功;迁移中未ack的消息由Redis暂存,新进程上线后重推
Nginx配置里proxy_read_timeout不是可选项,是保命参数
默认60秒的proxy_read_timeout在WebSocket场景下等于定时断连。只要连接空闲超过1分钟,Nginx就静默关闭它,后端收不到conn.Close()事件,连接泄漏,文件描述符耗尽。
正确做法是根据业务心跳周期反推:
- 若客户端每30秒发一次
ping,服务端pong响应,那么proxy_read_timeout至少设为90 - 金融行情类应用建议直接设为
86400(24小时),靠客户端主动心跳保活,而非代理层超时 - 必须同步配
proxy_http_version 1.1和proxy_set_header Connection "",否则Nginx无法复用后端连接,TIME_WAIT堆积
前端不能等断开才重连,要主动监控连接质量
WebSocket的onclose事件来得太晚——等它触发时,用户已经卡住5秒以上。前端该做的是“预判式切换”:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 创建连接后立即记录
onopen时间戳,若3秒内没触发,立刻ws.close()并调用/api/balancer/assign换节点 - 连接建立后,每10秒
ws.send({type:'ping'}),2秒没收到pong就标记该节点异常 - 每次重连前必须调用
/api/balancer/failover?from=node2通知后端,否则调度中心还在往故障节点分流量 - 迁移期间保持一个备用
WebSocket处于connecting状态,真正切换时延迟低于100ms
ip_hash不是万能的,会话亲和得靠token透传
ip_hash在Nginx里看着简单,但实际生产中问题很多:用户走移动网络IP频繁变化、公司出口NAT后所有员工共用一个IP、CDN回源地址不一致……这些都会导致同一用户被轮到不同后端,状态丢失。
更可靠的做法是让前端参与路由决策:
- 首次连接成功后,后端返回带签名的
node_id,前端存入sessionStorage - 后续请求(包括页面跳转后的重连)在URL query中带上
?node_id=sh-node2-abc123 - 后端校验签名后,强制路由到对应实例,不依赖IP
- 服务端下发
{"type":"migrate","to":"wss://node4.example.com/ws"}时,前端必须透传原始session token,否则新节点无法恢复上下文
平滑迁移最难的从来不是代码怎么写,而是状态在哪、谁负责清理、超时边界在哪——这三个问题没对齐,再漂亮的ws.close() + new WebSocket()也只是表面功夫。










