第 36 章 / 共 57 章 · 约 4 分钟
设计模式:实时更新
很多产品需要服务器主动把变化推给客户端:新消息、司机的位置、抢票队列的进度、文档里别人的光标。HTTP 默认是客户端问、服务器答,所以“实时更新”其实包含两个问题:最后一公里怎么把数据送到客户端,以及服务端内部怎么把一个事件送到持有那个用户连接的那台服务器。
1. 最后一公里:四种方式
| 方式 | 原理 | 适合 | 代价 |
|---|---|---|---|
| 短轮询 | 客户端每隔几秒请求一次 | 更新不频繁、几秒延迟可以接受 | 大量空请求;延迟取决于轮询间隔 |
| 长轮询 | 请求挂起,直到有新数据或超时再返回 | 中低频更新,不想改基础设施 | 每次返回后要重新建请求;服务器要挂住很多请求 |
| SSE(Server-Sent Events) | 一个 HTTP 长连接,服务器单向持续推送 | 只需服务器推送:通知、进度、行情、AI 流式输出 | 只能单向;客户端发消息仍走普通请求 |
| WebSocket | 升级后的全双工长连接 | 双向高频:聊天、协同编辑、游戏、位置共享 | 有状态的连接,负载均衡、扩缩容、重连都更复杂 |
选择的思路:能轮询就先轮询;只需要服务器推送就用 SSE;双向高频才上 WebSocket。 WebRTC 用于点对点音视频,一般系统设计题用不到。
2. 服务端内部:事件怎么找到连接
用户的长连接落在某一台网关服务器上。业务服务产生事件时,并不知道这个用户连在哪台网关。
- 连接注册表:网关在用户连上时写入
user_id → gateway_id(放在 Redis 里,带 TTL 并由心跳续期),业务服务查到网关后直接 RPC 投递。 - 按网关的发布订阅:每台网关订阅自己的频道,业务服务把消息发到“该用户所在网关”的频道。
- 按主题的发布订阅:每个房间或会话一个频道,订阅了这个频道的网关把消息转给本机上的相关连接。适合群聊、直播弹幕这类一对多场景。
- 一致性哈希固定分配:按 user_id 把用户固定分到某台网关,查表这一步就省了,但扩缩容时会有一批用户需要重连。
图表可横向滚动,展开后可缩放查看。
查看图表源码
flowchart LR
accTitle: 实时更新的服务端投递路径
svc["业务服务产生事件"] --> reg[("连接注册表:user → 网关")]
svc --> bus["发布订阅 / RPC"]
bus --> g1["网关 1"]
bus --> g2["网关 2"]
g1 -->|"WebSocket / SSE"| c1["用户 A 的客户端"]
g2 -->|"WebSocket / SSE"| c2["用户 B 的客户端"]
svc --> db[("持久存储:带序号的事件")]
c1 -.->|"重连后按序号补拉"| db
3. 不丢、不乱:可靠性要另外设计
- 推送只是加速通道,不是唯一来源:客户端断线期间的事件不会自动补上。每个事件写入持久存储并带上单调递增的序号;客户端记住收到的最后一个序号,重连后按序号补拉缺失部分。
- 推信号还是推数据:数据大或者变化频繁时,只推一个“有更新”的信号,让客户端自己去拉最新状态;消息本身就是内容时(聊天)才推完整数据。
- 移动端后台:App 进入后台后长连接会被系统断开,要退回到 APNs / FCM 推送通知。
- 重连风暴:一台网关重启,上万个客户端同时重连会打垮其他网关。客户端重连要带指数退避和随机抖动。
4. 数量级
- 一台调优过的服务器可以维持几万到几十万个空闲长连接,瓶颈通常是内存和文件描述符,而不是 CPU。
- 位置共享这类高频更新:每 4 秒一次、几百万在线,就是每秒几十万次写入,要按区域分片并在内存里处理。
- 负载均衡要支持长连接(L7 的 WebSocket 支持,或 L4),空闲超时要大于心跳间隔。
5. 常见追问
- 为什么不用短轮询? 几百万在线、每 2 秒轮询一次,就是每秒上百万次请求,大部分是空的。
- WebSocket 服务器扩容或发布时怎么办? 先停止接新连接、等待现有连接自然迁移,或者通知客户端带退避重连;客户端靠序号补拉,不丢消息。
- 一条消息要推给 100 万个订阅者(直播间)? 按房间分发到订阅了它的网关,每台网关只收一份,再本地扇出;必要时抽样或合并(每秒最多推 N 条弹幕)。
- 怎么知道用户在不在线? 心跳更新带 TTL 的在线状态;好友的在线状态变化可以批量、延迟推送,不必实时。
6. 本站用到这个模式的案例
- 案例 4:即时通讯:WebSocket 双向通信,按序号保证顺序,跨网关投递走 RPC 或按网关的发布订阅。
- 案例 5:网约车:派单、位置和状态变化经 WebSocket 推送,App 在后台时改用推送通知。
- 案例 6:通知系统:站内信写入收件箱,在线用户经 WebSocket 实时收到。
- 案例 3:信息流:通过 WebSocket 只推“有新内容”的信号,客户端再去拉取。
- 案例 12:票务抢购:等候室的排队进度用轮询或 SSE 展示。