实时通信架构选型:WebSocket、Server-Sent Events 与长轮询的工程权衡

发布时间:2026/7/9 16:52:16
实时通信架构选型:WebSocket、Server-Sent Events 与长轮询的工程权衡 实时通信架构选型WebSocket、Server-Sent Events 与长轮询的工程权衡一、实时通信的核心问题不是「能不能推消息」而是「在什么样的网络条件下、对多少用户、推消息的延迟和可靠性是多少」实时通信Real-time Communication听起来是一个技术问题但它背后的产品需求通常很具体聊天消息要「即时」到达、股票行情要「实时」更新、协作编辑要「毫秒级」同步、通知要「可靠」触达。不同的产品需求对应不同的实时通信方案而不同的方案在连接数、延迟、可靠性、实现复杂度和运维成本上的表现差异很大。WebSocket 是目前最完整的双向实时通信方案——它在单个 TCP 连接上提供全双工通信服务器和客户端可以随时互相发消息延迟最低。但它也有代价WebSocket 连接是「有状态的」如果服务端重启或者负载均衡器把连接切到另一个实例连接就断了需要重连WebSocket 连接会占用服务端资源每个连接一个 socket在高并发场景下需要仔细优化以及最重要的——WebSocket 可能被企业防火墙、代理或者移动网络的中间设备阻断因为这些设备不认识 WebSocket 协议。Server-Sent EventsSSE是「服务器向客户端单向推消息」的标准方案。它基于 HTTP用起来比 WebSocket 简单也更容易穿越防火墙和代理。但它只能服务器推客户端不能客户端发消息需要用普通的 HTTP 请求所以适合「通知、更新、日志流」等单向场景。长轮询Long Polling是最兼容的方案——它本质上就是「客户端发一个 HTTP 请求服务器不立即响应等到有新消息或者超时才返回」。它的延迟比 WebSocket 和 SSE 高因为每次响应后需要重新建立连接但兼容性最好所有浏览器、所有网络环境都支持。二、实时通信方案的技术对比与选型决策树flowchart TD A[实时通信需求] -- B{需要双向通信?} B -- 是 -- C{连接规模?} B -- 否 -- D[用 SSE] C -- 1万 并发 -- E[WebSocket 可行] C -- 1万 并发 -- F[考虑 SSE 普通 HTTP] E -- G{网络环境可控?} F -- H[用消息队列 推送服务] G -- 是 -- I[WebSocket] G -- 否 -- J[SSE 或者长轮询] D -- K[简单/兼容性好] J -- L[最大兼容性]选型决策的核心依据是以下维度双向 vs 单向如果客户端和服务器都需要主动发消息如聊天、协作编辑WebSocket 是最合适的如果只需要服务器推消息如通知、行情更新、日志流SSE 更简单。连接规模WebSocket 的每个连接占用一个服务端 socket 和一定的内存。在 Node.js 里默认情况下每个 WebSocket 连接占用约 30-50KB 内存1 万个连接就是 300-500MB再加上应用逻辑的内存单台机器的连接数上限通常在 5-10 万。如果需要支持百万级连接需要用专门为 WebSocket 优化的技术栈如 Erlang/Elixir 的 Phoenix Channels、Go 的 gorilla/websocket 连接池优化或者用云服务如 Pusher、Ably、AWS API Gateway WebSocket。网络环境如果用户在企业内网、在用移动网络、或者在不稳定的网络环境下WebSocket 连接可能会频繁断开。SSE 基于 HTTP在这种环境下更可靠。长轮询最可靠但延迟最高。实现复杂度WebSocket 需要服务端和客户端都实现一套连接管理逻辑连接建立、心跳保活、断线重连、消息ACKSSE 的服务端实现很简单就是一个不关闭的 HTTP 响应Content-Type 设为text/event-stream然后不断写入数据客户端也只需要处理onmessage事件长轮询的实现最简单但需要做「消息去重」和「连接超时处理」。三、WebSocket 生产环境的关键工程细节连接管理、扩缩容与消息可靠性WebSocket 在生产环境里最容易被低估的问题是「连接管理」。在一个 WebSocket 服务里你需要管理哪些用户连在哪个连接上、连接的健康状态心跳、连接断开时的清理、以及消息的投递保证消息发出去了但连接恰好断了怎么办。以下是一个用 Node.js ws库管理 WebSocket 连接的示例展示了连接管理、心跳和消息路由的基本模式import { WebSocketServer } from ws; const wss new WebSocketServer({ port: 8080 }); // 管理用户 ID 到 WebSocket 连接的映射 const userConnections new Map(); wss.on(connection, (ws, req) { // 从查询参数或者 Token 里提取用户 ID const userId authenticate(req); if (!userId) { ws.close(4001, Unauthorized); return; } // 注册连接 userConnections.set(userId, ws); // 心跳每 30 秒发一次 ping ws.isAlive true; ws.on(pong, () { ws.isAlive true; }); ws.on(message, (data) { // 处理客户端消息 const message JSON.parse(data); handleMessage(userId, message); }); ws.on(close, () { userConnections.delete(userId); }); }); // 心跳检测每 30 秒检查一次失败则断开 setInterval(() { wss.clients.forEach((ws) { if (ws.isAlive false) { return ws.terminate(); } ws.isAlive false; ws.ping(); }); }, 30000); // 发送消息给指定用户 export function sendToUser(userId, message) { const ws userConnections.get(userId); if (ws ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify(message)); } else { // 用户不在线存到离线消息表等用户上线后拉取 saveOfflineMessage(userId, message); } }这个示例里有两个关键设计心跳保活防止连接被中间设备静默断开和离线消息处理用户不在线时消息不能丢。生产环境中还需要考虑「同一用户多个设备同时在线」的情况需要维护一个用户 ID 到多个 WebSocket 连接的映射、以及「WebSocket 服务多实例部署」的情况需要用 Redis Pub/Sub 或者类似机制做跨实例的消息路由。四、SSE 与长轮询的实战简单场景的高性价比方案如果不是必须双向通信SSE 通常是比 WebSocket 更好的选择。SSE 的实现非常简单服务端返回一个不关闭的 HTTP 响应Content-Type 设为text/event-stream然后按需写入数据客户端用EventSourceAPI 监听消息。以下是一个 SSE 服务端的简单实现Node.js Expressapp.get(/api/notifications/stream, async (req, res) { res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); res.setHeader(Connection, keep-alive); // 发送心跳保持连接 const heartbeat setInterval(() { res.write(:\n\n); // SSE 注释行用于心跳 }, 30000); // 模拟有新通知时推送给客户端 const notify (notification) { res.write(data: ${JSON.stringify(notification)}\n\n); }; // 注册通知回调实际项目中用事件发射器或者消息队列 notificationEmitter.on(notify, notify); // 客户端断开时清理 req.on(close, () { clearInterval(heartbeat); notificationEmitter.off(notify, notify); }); });SSE 的客户端代码更简单const eventSource new EventSource(/api/notifications/stream); eventSource.onmessage (event) { const notification JSON.parse(event.data); showNotification(notification); }; eventSource.onerror () { // 自动重连是 EventSource 的内置行为 console.log(SSE 连接断开正在重连...); };长轮询适合「实时性要求不高几秒延迟可接受、但需要最大兼容性」的场景。实现方式很简单客户端发一个 HTTP 请求服务端 hold 住这个请求直到有新消息或者超时如 30 秒才返回客户端收到响应后立即发下一个请求。这个模式的延迟等于「服务端 hold 的时间 网络延迟」通常几秒内对于通知类场景是可接受的。五、总结实时通信架构选型不是「用最新的技术」而是「用最适合产品需求、网络环境和团队能力的方案」。WebSocket 提供最低延迟的双向通信但需要处理连接管理、扩缩容和防火墙穿透问题SSE 适合服务器单向推消息的场景实现简单、兼容性好长轮询兼容性最好但延迟最高。连接管理、心跳保活、断线重连、消息可靠投递和离线消息处理是把实时通信从「能跑」变成「能稳定跑」的五个关键工程细节。在用户量增长后WebSocket 服务的水平扩展多实例部署、连接迁移、消息路由会成为主要挑战需要提前规划。