即时通讯系统设计 (WhatsApp/微信)
即时通讯(IM)是现代互联网最核心的应用场景之一。WhatsApp 日处理 400 亿条消息,微信日活跃用户超 10 亿。设计一个支撑亿级用户的 IM 系统,考验的是对网络协议、消息可靠性、存储架构和分布式系统的综合理解。
一、核心需求分析
1.1 功能需求
- 一对一消息:用户 A 发消息给用户 B,B 实时收到
- 群聊消息:消息广播至群内所有在线成员
- 在线状态:展示用户在线/离线/最后在线时间
- 历史消息:新设备登录后可拉取历史消息
- 已读回执:消息已投递/已读状态同步
1.2 非功能需求
- 低延迟:消息端到端延迟 < 100ms(体感实时)
- 高可靠:消息不丢、不重、不乱序
- 高并发:支持亿级同时在线用户
- 跨设备:手机、PC、Web 同时在线,消息同步
二、通信协议选择
2.1 为什么不用 HTTP 短轮询?
传统的 HTTP 请求-响应模式不适合 IM:
- 客户端必须不断发请求检查新消息,浪费带宽和电量
- 延迟高:轮询间隔决定了消息送达的上限
- 服务端无法主动推送
2.2 长连接方案对比
| 方案 | 协议 | 优点 | 缺点 |
|---|---|---|---|
| WebSocket | TCP 全双工 | 浏览器原生支持、双向通信、连接复用 | 需要维护连接状态 |
| TCP 私有协议 | 自定义 | 性能最优、包体精简 | 客户端实现成本高 |
| MQTT | 发布-订阅 | 适合 IoT、QoS 分级 | 不直接支持群聊语义 |
推荐:移动端用 WebSocket + Protobuf 序列化,Web 端直接用 WebSocket。
2.3 WebSocket 连接管理
1 | 客户端 服务端 |
关键设计决策:
- 心跳间隔:30 秒发送一次 PING,60 秒未收到 PONG 判定连接断开
- 断线重连:指数退避延迟(1s → 2s → 4s → 8s,最大 60s)
- 连接迁移:用户切换网络时,携带 last_event_id 在新连接上继续收取未 ACK 的消息
三、消息可靠投递
3.1 消息生命周期
1 | 发送方 → 网关 → 消息队列 → 接收方 |
每条消息的投递状态:
1 | SENT → DELIVERED → READ |
3.2 离线消息处理
当接收方离线时:
- 消息存入接收方的离线消息队列(Redis List / MySQL)
- 接收方上线后,从 last_event_id 开始拉取
- 拉取完成后,标记这些消息为 DELIVERED
消息 ID 设计:使用 Snowflake 算法生成全局递增 ID(64 位,毫秒级时间戳 + 机器 ID + 序列号),保证同一会话内消息有序。
3.3 消息去重
由于网络重试,同一条消息可能被投递多次。通过以下机制保证幂等:
| 机制 | 说明 |
|---|---|
| 客户端生成 msg_id | 发送方生成 UUID 作为消息唯一标识 |
| 服务端去重 | 收到消息后,Redis SETNX msg:{msg_id} 检查是否已处理 |
| 接收方去重 | 接收方本地维护已接收消息 ID 集合,丢弃重复消息 |
四、群聊消息分发
4.1 写扩散 vs 读扩散
群聊的核心难题:一条消息要送达成千上万个群成员。
| 方案 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 写扩散 | 发消息时为每个群成员写一条收件箱记录 | 读简单、延迟低 | 大群写入放大严重(万人群 = 万次写入) |
| 读扩散 | 消息只写一份到群的 timeline | 写简单、存储省 | 每个成员上线都读群消息,读压力大 |
| 混合策略 | 小群写扩散 + 大群读扩散 | 平衡性能 | 实现复杂 |
WhatsApp 的做法:小群(< 100 人)写扩散,大群(≥ 100 人)读扩散。
4.2 实现细节
小群写扩散流程:
1 | 1. 用户发消息 → 查群成员列表 |
大群读扩散流程:
1 | 1. 用户发消息 → 写入群的 timeline(MySQL 表) |
五、存储架构
5.1 消息存储
| 数据类型 | 存储方案 | 说明 |
|---|---|---|
| 在线消息 | Redis Stream / Kafka | 实时投递,TTL 短(24h) |
| 离线消息 | Redis List + MySQL | 离线消息先存 Redis,定期归档到 MySQL |
| 历史消息 | MySQL + 冷热分离 | 近 7 天热数据,更早的数据归档到对象存储 |
| 附件/图片 | 对象存储 (S3/OSS) + CDN | 缩略图、原图分别存储 |
5.2 用户状态
1 | Redis Hash: |
在线状态通过心跳维护。定期(每 30s)更新 last_seen 时间戳,超过 60 秒未更新标记为离线。
六、系统架构总览
1 | ┌──────────────┐ |
七、扩展性考量
- 接入层无状态:WebSocket 网关只负责连接管理和协议转换,业务逻辑下沉到消息处理层
- 按用户 ID 分片:消息队列按 user_id 哈希分区,保证同一用户的消息有序处理
- 消息 ID 递增:Snowflake 生成全局递增 ID,客户端用 ID 判断消息顺序
- 多机房部署:用户就近接入,消息通过专线同步到异地机房
- 容量评估:假设 1 亿用户、人均日发送 50 条消息,日均 50 亿条消息,峰值 QPS ≈ 50亿 / 86400 × 1.5(峰值系数) ≈ 8.7 万 QPS
7.2 WhatsApp 的架构哲学
WhatsApp 后端能支撑 400 亿日消息量,依靠几个核心设计决策:
每连接一个 Erlang 进程,无连接池。传统后端用连接池复用 TCP 连接,WhatsApp 反其道而行——每个用户连接映射为一个轻量级 Erlang 进程(约 300 字节),不池化、不多路复用。这在其他语言下是不可行的(Java 线程需 ~1MB 栈),但 BEAM 虚拟机的进程模型让”百万进程一台机器”成为现实。
全异步消息传递。所有内部通信都是异步的——进程发送消息后立即继续处理下一件事,不等待回复。这避免了传统同步调用中的线程阻塞问题。
单向复制。数据从主数据中心单向复制到备用数据中心。备用节点只读,不处理写入。当主数据中心宕机时,备用节点中的进程状态会自动重新创建(Erlang Supervisor 树的设计),而非依赖复杂的主备切换。
ETS 内存表 + 直写缓存。WhatsApp 后端的许多集群使用 ETS(Erlang Term Storage,内存共享表)作为内部数据存储。配合直写缓存模式,读操作走内存表,写操作同时更新内存表和持久化存储——用空间换时间,极致优化读取延迟。
八、小结
即时通讯系统的核心挑战是消息可靠性(不丢不重不乱序)和实时性(毫秒级端到端延迟)。关键架构决策包括:WebSocket 长连接、消息 ID 去重、小群写扩散/大群读扩散的分发策略、以及 Redis + MySQL 的分层存储。WhatsApp 通过 Erlang/OTP 构建的分布式 Actor 模型天然适合这种场景,而微信则选择了 C++ 协同程序 + 同步 RPC 的技术路线——两种方案都在亿级规模上证明了各自的可行性。