实时通信选型: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 组合
总结
| 维度 | WebSocket | SSE | Webhook |
|---|---|---|---|
| 通信方向 | 双向 | 单向(服务端→客户端) | 单向(服务端→客户端) |
| 连接类型 | 长连接 | 长连接 | 短连接(HTTP 回调) |
| 浏览器支持 | 原生支持 | 原生支持(EventSource) | 不适用 |
| 自动重连 | 需自行实现 | 原生支持 | 需自行实现 |
| 水平扩展 | 需要消息队列 | 需要消息队列 | 天然支持 |
| 实现复杂度 | 高 | 低 | 低 |
| 适用场景 | 聊天、协同、游戏 | 通知、推送、日志 | 回调、事件、CI/CD |
实时通信的选型不是”哪个更先进”,而是”哪个更匹配你的数据流方向”。 大部分场景下,SSE 和 Webhook 的组合就能覆盖需求,不需要 WebSocket 的复杂度。
需要实时通信方案设计或后端开发?联系我们,说清你的通信场景与并发规模,24 小时内回可行性。
相关阅读
- REST vs GraphQL vs gRPC:后端 API 协议选型的决策框架 —— API 通信协议的上层选型参考
- API 安全实战:认证、授权与常见攻击防护 —— 实时通信接口的安全配置
常见问题
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 服务器之间广播消息,确保用户连接到任意一台服务器都能收到消息。