← 返回博客

实时通信选型:WebSocket、SSE 与 Webhook 的工程决策

实时通信不是只有 WebSocket。本文从推送方向、连接模型、重连机制三个维度对比 WebSocket、SSE(Server-Sent Events)和 Webhook,给出不同场景的选型建议和实现要点——适合需要实时推送、事件通知或双向通信的后端开发者和架构师。

先说结论:选实时通信方案,先看推送方向

实时通信有三个主流方案:WebSocket、SSE(Server-Sent Events)、Webhook。选择的标准不是”哪个更新”,而是”你的数据往哪个方向流”。

方案通信方向连接模型适用场景
WebSocket双向长连接聊天、协同编辑、游戏、实时仪表盘
SSE服务端→客户端长连接消息通知、数据推送、日志流
Webhook服务端→客户端回调支付回调、CI/CD 通知、事件订阅

1. WebSocket:双向通信,但复杂度最高

适用场景

  • 聊天/即时通讯(需要双向实时交互)
  • 协同编辑(多端同步操作)
  • 实时游戏(低延迟双向通信)
  • 实时仪表盘(服务端主动推送数据更新)

实现要点

连接管理:WebSocket 是长连接,需要维护连接池。每次客户端连接时,在服务端记录连接 ID 和用户 ID 的映射关系。

const connections = new Map<string, WebSocket>();

wss.on('connection', (ws, req) => {
  const userId = parseUserId(req);
  connections.set(userId, ws);
  ws.on('close', () => connections.delete(userId));
});

心跳检测:WebSocket 连接可能因为网络问题断开而不触发 close 事件,需要定期发心跳包检测连接是否存活。

// 服务端每 30 秒发一次 ping
setInterval(() => {
  connections.forEach((ws, id) => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.ping();
    } else {
      connections.delete(id);
    }
  });
}, 30000);

水平扩展:单机 WebSocket 连接数有限,水平扩展时需要用 Redis Pub/Sub 或消息队列广播消息:

// 收到消息后,通过 Redis 广播到所有 WebSocket 服务器
redis.publish('chat:channel', JSON.stringify(message));

// 每台 WebSocket 服务器订阅频道,推送给本地连接的用户
redis.subscribe('chat:channel', (message) => {
  const { userId, content } = JSON.parse(message);
  const ws = connections.get(userId);
  if (ws) ws.send(content);
});

2. SSE:服务端推送,简单够用

适用场景

  • 消息通知(新消息提醒、系统通知)
  • 数据推送(股票价格、日志流、进度更新)
  • 实时数据更新(仪表盘、监控面板)

实现要点

SSE 的实现比 WebSocket 简单得多——服务端只需要设置正确的 Content-Type 并保持连接打开:

// 服务端
app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  // 每 5 秒推送一次数据
  setInterval(() => {
    res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
  }, 5000);
});
// 客户端
const eventSource = new EventSource('/events');
eventSource.onmessage = (e) => {
  console.log('收到推送:', JSON.parse(e.data));
};

SSE 最大的优势是浏览器原生支持自动重连——断线后浏览器会自动尝试重连,不需要自己实现重连逻辑。这是 WebSocket 不具备的。

SSE 的限制

  • 单向:客户端不能通过同一连接向服务端发送数据
  • 浏览器连接数限制:同一域名下最多 6 个 SSE 连接(HTTP/1.1),HTTP/2 下没有限制
  • 不支持二进制数据(可以通过 Base64 编码绕过)

3. Webhook:事件通知,一夜回到 HTTP

适用场景

  • 支付回调(支付宝/微信支付完成后通知你的服务器)
  • CI/CD 通知(代码推送后触发构建)
  • 事件订阅(第三方服务有事件时通知你)

实现要点

Webhook 不是长连接,而是服务端通过 HTTP POST 回调客户端的 URL。所以它没有”实时”那么实时,但胜在简单可靠。

重试机制:客户端不可达时,通知会丢失。需要实现重试策略:

async function sendWebhook(url: string, payload: any) {
  const maxRetries = 3;
  for (let i = 0; i < maxRetries; i++) {
    try {
      await axios.post(url, payload, { timeout: 5000 });
      return;
    } catch (err) {
      // 指数退避:1s, 2s, 4s
      await sleep(Math.pow(2, i) * 1000);
    }
  }
  // 重试失败,记录到事件日志,人工处理
  await logFailedEvent(url, payload);
}

签名验证:Webhook 回调可能被伪造,客户端需要验证回调的合法性。通常用 HMAC 签名:

// 服务端签名
const signature = crypto
  .createHmac('sha256', secret)
  .update(JSON.stringify(payload))
  .digest('hex');

// 客户端验证
function verifySignature(payload: any, signature: string): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');
  return expected === signature;
}

4. 选型决策树

你的场景需要实时通信吗? ├── 否 → REST API 轮询(简单够用) └── 是 → 推送方向是? ├── 双向 → WebSocket ├── 服务端→客户端 → 连接持久性? │ ├── 长连接 → SSE │ └── 短连接(回调)→ Webhook └── 混合 → WebSocket + Webhook 组合


总结

维度WebSocketSSEWebhook
通信方向双向单向(服务端→客户端)单向(服务端→客户端)
连接类型长连接长连接短连接(HTTP 回调)
浏览器支持原生支持原生支持(EventSource)不适用
自动重连需自行实现原生支持需自行实现
水平扩展需要消息队列需要消息队列天然支持
实现复杂度
适用场景聊天、协同、游戏通知、推送、日志回调、事件、CI/CD

实时通信的选型不是”哪个更先进”,而是”哪个更匹配你的数据流方向”。 大部分场景下,SSE 和 Webhook 的组合就能覆盖需求,不需要 WebSocket 的复杂度。

需要实时通信方案设计或后端开发?联系我们,说清你的通信场景与并发规模,24 小时内回可行性。

相关阅读

常见问题

WebSocket 和 SSE 最核心的区别是什么?

WebSocket 是双向通信——客户端和服务端可以随时向对方发送消息。SSE 是单向的——服务端推送消息到客户端,客户端不能通过同一连接向服务端发送消息。如果只需要服务端推送通知(如消息提醒、数据更新),SSE 就够了,实现简单、自动重连、兼容 HTTP/2。如果需要双向交互(如聊天、协同编辑、游戏),才需要 WebSocket。

Webhook 和轮询的区别是什么?

轮询是客户端定期主动查询服务端"有没有新数据"——不管有没有新数据,每次查询都会消耗资源。Webhook 是服务端在有新数据时主动通知客户端——更高效,但需要客户端暴露一个可接收回调的 URL。Webhook 的缺点是:如果客户端不可达(离线、网络故障),通知会丢失。所以 Webhook 通常配合重试机制和事件日志使用。

实时通信的场景下,重连策略怎么设计?

推荐指数退避重连(Exponential Backoff):断线后先等 1 秒重试,失败再等 2 秒、4 秒、8 秒……直到最大间隔(如 30 秒)。每次重连时带上上次收到的消息 ID,服务端从该 ID 之后的消息开始推送,避免重复消费。WebSocket 和 SSE 都支持自动重连,但需要自己实现退避逻辑。

高并发场景下 WebSocket 的连接数怎么管理?

单机 WebSocket 连接数上限取决于操作系统文件描述符限制和内存。一台 4C8G 的服务器大约能维持 5-10 万 WebSocket 连接。超过这个量级需要做水平扩展:用 Redis Pub/Sub 或消息队列(如 RabbitMQ、Kafka)在多个 WebSocket 服务器之间广播消息,确保用户连接到任意一台服务器都能收到消息。

本文来自 AI Enable Harness 一线交付实践。需要同类系统或优化服务?

📡 本文同步发布平台: CSDN 知乎

订阅博客更新

新文章发布后第一时间邮件通知。不定期发送,不推销。

订阅 →