← 返回文章列表

WebSocket 与轮询:实时通信怎么选

网络入门

三种方式

方式机制缺点
短轮询定时发请求问有没有新消息空跑请求多、延迟高
长轮询请求挂起直到有数据才返回仍每次重建、服务端要hold连接
WebSocket一次握手后全双工长连连接需维护、有状态

怎么选

  • 低频、可接受延迟:短轮询最简单;
  • 准实时、单向推送:长轮询够用,且对代理友好;
  • 高频双向、聊天 / 行情:WebSocket 最合适。

WebSocket 要注意

  1. 心跳:定时发 ping,否则中间代理可能以空闲断开;
  2. 断线重连:网络抖动后要指数退避重连,避免打爆服务端;
  3. 横向扩展:多实例时要靠消息总线把消息广播到持有该连接的节点。

实战案例:三个“连上了却不稳”

  1. 几分钟后集体掉线:Nginx 默认 proxy_read_timeout 为 60 秒,空闲长连被断开。修复:调大超时,并让客户端定时心跳。
  2. 重连风暴打垮服务:网络恢复后所有客户端同时重连。修复:指数退避加随机抖动,服务端限制单 IP 建连速率。
  3. 多实例消息发丢:用户连在 A 实例,消息却由 B 实例产生,B 上并没有那条连接。修复:用 Redis Pub/Sub 或消息总线广播,或按用户做一致性路由。

常见问题(FAQ)

WebSocket 能带鉴权头吗?浏览器原生 WebSocket 不支持自定义请求头,常见做法是在握手 URL 上带一次性短期令牌,或用 Cookie 校验。断线期间的消息会补发吗?不会,长连不保证投递;需要消息序号加历史拉取来补齐。能用 SSE 代替吗?只要服务端单向推送,SSE 更简单,自带重连且走普通 HTTP。WebSocket 一定比轮询省资源吗?只有在连接稳定、消息频繁时才省;低频场景维持长连反而更贵。

长连接的容量估算

WebSocket 的瓶颈往往不在带宽,而在连接数与内存:

  • 每连接内存:单连接常占用几十到上百 KB(含缓冲区与运行时开销),一进程数万连接需要按此估算内存上限;
  • 文件描述符:每个连接占一个 fd,需要同步调高进程与系统的 ulimit,否则会在达到上限时报错;
  • 心跳放大:若每条连接每 30 秒发一次心跳,10 万连接意味着每秒数千条心跳消息,需要评估消息处理与广播开销;
  • 有状态带来的运维成本:滚动发布时必须优雅摘流并让客户端重连,否则会引发重连风暴;扩缩容要考虑连接迁移;
  • 下行广播:一次广播给所有连接会瞬间放大出网流量,热门场景应按订阅分组只推给相关连接。

因此常见做法是:只有在消息频率高、双向交互明确的场景才用长连接;低频通知优先用 SSE 或推送服务,把连接维护成本交给更擅长的系统。

客户端侧的实现要点

  1. 重连要带状态恢复:重连成功只是第一步,还需要按消息序号或时间戳补齐断线期间缺失的内容,否则用户会看到不连续的数据。
  2. 区分“未连接”与“无数据”:界面应能提示当前连接状态,避免把网络中断显示成“暂无数据”,让用户做出错误判断。
  3. 页面切换与息屏要处理:移动端切到后台后连接可能被系统回收,回到前台应主动检查并重建连接,而不是依赖底层自动恢复。
  4. 限制消息体积:单条消息过大时会阻塞其它消息的处理,应把大对象改为引用加按需拉取,或分片传输。
  5. 发送要排队与限流:客户端在弱网下可能堆积发送请求,应有发送队列与上限,避免恢复连接后瞬间灌入大量消息。

长连接的复杂性大部分在客户端的状态管理上,把连接状态、数据一致性与用户体验三者明确区分开,实现会清晰很多。