轮询 / 长轮询 / 实时通信选型
实时通信的方案很容易一上来就被问成“到底用 WebSocket 还是 SSE”。
但真实项目里,通常至少有四条路线:
- 轮询
- 长轮询
- SSE
- WebSocket
更稳的做法,不是先选技术名词,而是先判断:
- 需要单向还是双向通信
- 更新频率有多高
- 服务端和网关能承受什么连接模型
- 前端是否要处理重连、心跳、状态恢复
先看一句话版
| 方案 | 通信方向 | 连接模型 | 适合场景 |
|---|---|---|---|
| 轮询 | 客户端主动拉 | 短连接 | 更新频率低、实现要简单 |
| 长轮询 | 客户端主动拉 | 单次请求挂起更久 | 更新频率中等、环境受限 |
| SSE | 服务端单向推 | 长连接 | 流式输出、通知、日志流 |
| WebSocket | 双向通信 | 长连接 | 聊天、协同编辑、实时互动 |
1. 轮询
轮询很好理解:客户端按固定间隔发请求,问一次有没有新数据。
async function poll() {
const res = await fetch('/api/tasks/status')
const data = await res.json()
console.log(data)
}
setInterval(poll, 5000)
轮询的优点
- 实现最简单
- 对老系统和旧网关友好
- 出问题时排查路径短
- 服务端不需要维护大量长连接
轮询的代价
- 更新不够实时
- 请求频率高时浪费资源
- 空数据响应也会持续产生流量
- 多客户端同时轮询时,服务端压力会抬起来
轮询适合什么场景
- 任务状态查询
- 后台报表刷新
- 低频运营面板
- 实时要求不高的状态更新
2. 长轮询
长轮询和普通轮询的差别,不在“有没有继续请求”,而在“单次请求会等更久”。
服务端如果暂时没有新数据,不会立刻返回,而是会挂住一段时间;一旦有数据或超时,再返回响应,客户端收到后再发下一次。
async function longPoll() {
const res = await fetch('/api/notifications/subscribe')
const data = await res.json()
console.log(data)
longPoll()
}
longPoll()
长轮询的优点
- 比普通轮询更接近实时
- 空转请求更少
- 仍然基于普通 HTTP,很多基础设施更容易接住
长轮询的代价
- 服务端要维护挂起请求
- 并发一高,资源占用就会上来
- 归根到底还是客户端不断重建请求
- 双向通信能力还是弱
长轮询适合什么场景
- 系统环境不方便上 WebSocket
- 需要比轮询更及时
- 但实时规模还没有大到要维护专门长连接体系
3. SSE
SSE,Server-Sent Events,是 服务端单向持续推送。
const es = new EventSource('/api/stream')
es.onmessage = (event) => {
console.log(event.data)
}
SSE 的优点
- 服务端持续推,客户端接收简单
- 天然适合流式输出
- 浏览器原生支持
EventSource - 自动重连模型比 WebSocket 更直接
- 基于 HTTP,很多代理和网关更容易兼容
SSE 的代价
- 单向通信
- 原生
EventSource不支持自定义请求头 - 复杂交互和房间模型不如 WebSocket 顺