凌晨流量高峰,客户端从地铁切换到蜂窝网络,WebSocket 看似在线,前端代理却因 60 秒无数据主动断开;重连后,同一条订单消息又被消费一次。问题通常不在网络“断没断”,而在系统把抖动误判成死亡,又没有保存可续接的会话位置。
如果你正在选机器,或者想先把线路和配置跑通再谈长期方案,可以先看看这几家的线路说明和技术参数:野草云(全球云服务器),先看清带宽线路、流量策略和退款条款,再决定要不要迁。
一、心跳如何判断连接是否真的死亡
心跳不是定时发送一个空包就结束。服务端应记录 lastSend、lastPong 和最后一条有效业务消息时间,并给探测包加入 nonce,确认返回对应当次请求。若入口代理的空闲超时为 60 秒,心跳间隔可先取 20 至 25 秒,再结合链路 RTT 的 P95 与重连耗时校准。

断线判断应采用连续失败阈值,而不是一次超时就切换状态。客户端可按 OPEN、SUSPECT、RECONNECTING、CLOSED 推进:连续丢失 2 至 3 次心跳才进入 SUSPECT,收到有效响应立即恢复;超过重连窗口且仍无响应,才认定连接死亡。业务消息比心跳更能证明链路存活,因此两者不能共用一个简单的“最后通信时间”。
判断心跳参数时,要同时观察代理日志、TCP 关闭原因、心跳响应率和客户端重连耗时。协议级 Ping 有时会被代理提前终结,应用层探测更可控;但探测过于频繁又会挤占弱网带宽。常见坑是所有客户端共用固定间隔、只统计平均延迟,以及把 ACK 丢失直接当作服务端宕机。
二、会话续接怎样避免重复消费
重连不能只换一个新连接 ID。客户端应携带可恢复的 sessionId、消费序号和最后确认位点,服务端从共享存储读取检查点,只补发尚未确认的消息。若会话已超过恢复有效期,则先做一次全量对账,不能默认从头重放。网络断开与消息重复是两件事:前者触发续接,后者由稳定的幂等键拦截。

消息去重键应来自业务主键、事件 ID 或“会话与序号”的稳定组合,不能使用每次重试都会变化的随机值。消费端应先落库去重记录,再更新业务状态;ACK 丢失时,服务端再次投递相同序号,客户端识别为已处理并返回当前位点。去重记录还需设置合理保留期,覆盖最长重试周期和消息重放周期。
滚动发布时,旧实例先停止接收新连接,再等待存量连接排空或主动迁移;新实例通过共享检查点接手会话,并使用递增的 epoch 标记接管代次。若续接请求落到旧实例,它只能确认已提交位点,不能继续投递。发布期间最忌讳本地保存消费进度、实例之间复制不及时,以及新旧版本采用不同消息格式,这些都会让“已处理”重新变成“待处理”。
断线状态机与会话续接
WebSocket心跳判定不能只看 socket 是否存在。客户端应记录最后收帧、发帧和最近 pong 的时间;空闲时发送应用层 ping,底层客户端也可使用协议 ping,超过约定窗口仍无响应,就关闭旧连接。发送 ping 后不能刷新活跃时间,否则网络黑洞会被假心跳掩盖。服务端也要清理半开连接,并保证代理空闲超时大于心跳周期,控制帧需能原样透传。
重连的本质是会话续接,不是重新登录。服务端保存递增序号、已确认序号和短期重放缓冲;客户端携带 resumeToken 与 lastAck 发起续接,服务端校验令牌、权限和会话归属后,从确认点补发。令牌过期或会话迁移时,应返回需重新鉴权的明确结果,不能让客户端在不确定状态下继续发送。
在消息去重上,最常见的坑是把重复投递当成重复执行。消息必须带全局唯一 ID,接收端先写幂等记录再处理;序号只用于排序和发现缺口,不能替代业务幂等。乱序、缺号或超出重放窗口时,应拉取全量快照。断线状态机要区分 disconnected、connecting、recovering、online,失败后按带抖动的退避策略重试,且同一时刻只能有一个建连任务。

滚动发布衔接与重连治理
滚动发布不能直接杀掉旧进程。旧实例先停止接收新连接,再通知客户端短时重连并排空队列;连接归零、请求清空且代理摘流后才退出。多实例下,会话和去重记录应放在共享存储,或提供可靠的实例迁移;仅存于单机内存中的状态,会在 Pod 漂移或扩容时失效。新旧版本还需兼容鉴权格式、消息字段和心跳语义。
重连风暴常由实例同时重启造成。客户端要使用指数退避、随机抖动和并发上限,服务端增加连接速率限制,并监控重连率、恢复成功率、重复消息数、心跳超时数、队列积压和发布期在线数。协议不兼容时应明确要求重新登录,避免反复恢复失败。
上线前检查清单:模拟客户端断网、代理丢包、Pod 被杀和滚动发布;确认心跳能判死且不误报;验证令牌过期、序号缺口、乱序、重复包和会话迁移都返回确定结果;核对摘流后才终止实例;压测恢复速率与幂等;为重连率突增、恢复失败率和积压设置告警;保留强制刷新及降级轮询开关,并记录演练结果。


喜欢
顶
难过
囧
围观
无聊





