---
title: 基于微服务架构实现亿级用户高并发低延迟的Web版WhatsApp实时消息应用系统设计分析
created: 2026-03-11
updated: 2026-03-11
type: summary
tags: ["design", "messaging", "notes", "scalability", "telegram"]
external: http://go/ui/posts/2145/
status: inbox
---

## 设计一个Web版WhatsApp应用：系统设计分析

### 1. 需求澄清
**功能需求：**
- 用户注册/登录（手机号验证）
- 联系人管理（添加好友、黑名单）
- 一对一实时消息（文本、图片、文件、语音）
- 群组聊天（创建群、加入群、@成员、群公告）
- 消息状态（已发送、已送达、已读）
- 离线消息：用户离线时消息暂存，上线后自动接收
- 多端同步（Web、手机App）
- 消息历史记录（可回溯）

**非功能需求：**
- **高并发**：支撑亿级日活用户，千万级同时在线
- **低延迟**：消息端到端延迟<100ms
- **高可用**：99.99%可用性，容灾能力强
- **数据一致性**：消息不丢失、不重复、顺序一致
- **安全性**：传输加密（TLS）、认证授权、防止恶意攻击

### 2. 数据估算
- **日活用户**：1亿
- **人均每日消息数**：20条 → 每日消息总量 = 20亿条
- **峰值QPS**：假设消息集中在白天12小时，平均约4600条/秒，峰值按5倍计 ≈ 2.3万条/秒（实际可能更高，如节假日）
- **存储**：每条消息平均1KB（文本+元数据），每日新增约20TB；图片/文件使用对象存储，按平均每人每天发送1张图片（100KB）计，每日新增10PB（需压缩和去重）
- **连接数**：同时在线用户按DAU的20%计 ≈ 2000万WebSocket长连接

### 3. 高层架构设计
采用微服务架构，分层设计，核心组件如下：

```
客户端（浏览器） → CDN（静态资源） → 负载均衡（LVS/ Nginx） → 网关层（API Gateway）
                                                                  ├─ WebSocket服务集群（处理长连接）
                                                                  ├─ HTTP API服务（用户、关系、群组等）
                                                                  └─ 管理/监控服务
后端服务：用户服务、关系服务、群组服务、消息服务、文件服务、推送服务
数据层：关系数据库（MySQL/TiDB）、缓存（Redis）、消息队列（Kafka）、对象存储（S3）
```

**架构图描述：**
- **接入层**：DNS解析至CDN加速静态资源；动态请求通过LVS+ Nginx负载均衡分发至API网关。
- **网关层**：负责路由、认证限流；将HTTP请求转发给业务服务，将WebSocket升级请求转发给WebSocket集群。
- **业务服务**：无状态，水平扩展；每个服务独立数据库（或分库）。
- **数据层**：
  - **缓存**：Redis集群存储用户在线状态、会话信息、热点数据。
  - **数据库**：MySQL分库分表（或TiDB分布式）存储用户、关系、群组、消息等。
  - **消息队列**：Kafka削峰填谷，解耦消息写入和推送。
  - **对象存储**：MinIO或AWS S3存储图片、文件。

### 4. 详细设计

#### 4.1 数据模型
**用户表（user）**
```sql
user_id BIGINT PRIMARY KEY,
phone VARCHAR(20) UNIQUE,
name VARCHAR(100),
password_hash CHAR(60),
avatar_url TEXT,
last_seen TIMESTAMP,
created_at TIMESTAMP
```

**联系人表（contact）**
```sql
user_id BIGINT,
contact_id BIGINT,
status TINYINT, -- 0:待确认 1:已添加 2:黑名单
created_at TIMESTAMP,
PRIMARY KEY (user_id, contact_id)
```

**群组表（group）**
```sql
group_id BIGINT PRIMARY KEY,
name VARCHAR(100),
owner_id BIGINT,
avatar_url TEXT,
created_at TIMESTAMP
```

**群组成员表（group_member）**
```sql
group_id BIGINT,
user_id BIGINT,
role TINYINT, -- 0:普通 1:管理员 2:群主
joined_at TIMESTAMP,
last_read_msg_id BIGINT, -- 该成员在群中最后已读的消息ID
PRIMARY KEY (group_id, user_id)
```

**会话表（conversation）**  
一对一聊天也抽象为会话，便于统一处理。
```sql
conv_id BIGINT PRIMARY KEY,
type TINYINT, -- 1:一对一 2:群组
target_id BIGINT, -- 若type=1，target_id为对方用户ID；若type=2，target_id为群组ID
created_at TIMESTAMP
```

**会话成员表（conv_member）**  
记录用户参与的会话及其最后已读位置。
```sql
conv_id BIGINT,
user_id BIGINT,
last_read_msg_id BIGINT,
joined_at TIMESTAMP,
PRIMARY KEY (conv_id, user_id)
```

**消息表（message）**  
采用读扩散，每条消息只存一份。
```sql
msg_id BIGINT PRIMARY KEY, -- 雪花算法生成，带时间戳
conv_id BIGINT,
sender_id BIGINT,
content_type TINYINT, -- 0:文本 1:图片 2:文件...
content TEXT, -- 文本内容或文件URL
created_at TIMESTAMP,
deleted BOOLEAN DEFAULT FALSE
```
索引：`(conv_id, msg_id)` 用于会话内消息拉取；`(sender_id, created_at)` 用于用户历史消息。

**离线消息处理**：不单独建表，通过 `conv_member.last_read_msg_id` 结合消息表拉取未读消息。

#### 4.2 实时通信（WebSocket）
- **连接管理**：每个WebSocket服务器维护本地连接池（userId → connection）。同时将用户在线状态写入Redis（`online:userId` 记录服务器节点ID），便于跨节点推送。
- **握手认证**：客户端通过HTTP请求获取JWT，WebSocket连接时在query参数或头部携带token，服务器验证并建立长连接。
- **心跳**：每隔30秒发送ping/pong，超时则断开，清理Redis状态。

#### 4.3 消息发送流程（一对一）
1. **客户端A** 通过WebSocket发送消息，格式：`{ "to": "userB", "content": "hello", "type": "text" }`
2. **WebSocket服务器** 收到后，解析接收方ID，生成全局唯一 `msg_id`（雪花算法），构造消息对象并写入Kafka（topic: `message_send`）。
3. **Kafka消费者**（消息服务）处理：
   - 获取会话ID：若A与B已有会话，则使用；否则创建新会话（插入conversation和conv_member表）。
   - 将消息插入message表（`conv_id`, `msg_id`, `sender_id`, ...）。
   - 更新会话的`last_msg_time`等（可选）。
   - 调用推送服务。
4. **推送服务**：
   - 从Redis查询B的在线状态：若在线，获取所在WebSocket服务器节点，通过内部RPC（gRPC）转发消息至该节点，由节点通过WebSocket推送。
   - 若离线，则不做处理（等待B上线后拉取）。
5. **客户端B** 收到消息后，发送已送达回执（可自动发送），服务器更新消息状态（如送达时间）。已读回执由用户主动触发。

#### 4.4 群组消息
- 发送者A发消息到群组G，消息中包含群ID。
- 服务器处理流程类似，但推送时需遍历群组成员（从缓存或DB获取成员列表），对每个在线成员推送。离线成员无需推送。
- 为避免遍历大群影响性能，可异步处理：消息写入后，将推送任务提交到线程池或Kafka，由多个worker并发推送。
- 对于超大群（如万人），可采用“只推送活跃在线成员”策略，或使用消息总线（如Redis Pub/Sub）广播，但需注意网络开销。

#### 4.5 离线消息拉取
用户上线流程：
1. 客户端建立WebSocket连接后，发送`online`事件。
2. 服务器更新Redis在线状态，并触发离线消息拉取。
3. 客户端调用HTTP API `GET /messages/offline?since=timestamp` 或通过WebSocket请求拉取。
4. 服务器端：
   - 查询用户参与的所有会话（从conv_member表）。
   - 对于每个会话，获取`last_read_msg_id`，执行SQL：`SELECT * FROM message WHERE conv_id = ? AND msg_id > ? ORDER BY msg_id ASC LIMIT 1000`（分页）。
   - 聚合所有消息，按时间排序返回给客户端。
5. 客户端接收消息后，显示并发送已读回执，服务器更新对应会话的`last_read_msg_id`。

优化：可预先计算每个用户的未读消息数量（用Redis计数器），上线时先推送数量，再按需拉取。

#### 4.6 消息状态与一致性
- **消息顺序**：客户端根据`msg_id`（雪花算法自带时间戳）排序，确保全局递增。
- **去重**：客户端缓存已接收的`msg_id`，重复则丢弃。
- **可靠性**：Kafka确保消息不丢失；数据库写入采用事务；若推送失败，可通过重试机制补偿。
- **已读回执**：客户端在用户阅读消息后发送`read`事件，服务器更新对应会话的`last_read_msg_id`。对于群组，每个成员独立维护。

#### 4.7 高并发支持策略
- **水平扩展**：
  - WebSocket服务器无状态，可随意增加实例，通过Nginx的ip_hash或一致性哈希负载均衡保持用户连接固定？实际上，WebSocket需要长连接，最好使用四层负载均衡（如LVS）直接转发，但跨节点推送需要Redis映射。可以使用一致性哈希让同一用户始终连接到同一服务器，减少跨节点推送，但容灾时可能重连。权衡后，我们采用随机分发+Redis映射，跨节点推送通过内部RPC。
  - 业务服务无状态，水平扩展。
- **缓存**：
  - 用户信息、会话信息、群组成员列表缓存在Redis，减少DB压力。
  - 在线状态用Redis存储，过期时间设为心跳超时的2倍。
- **数据库分片**：
  - 消息表按`conv_id`哈希分片（如64个库，每个库多表）。
  - 用户表按`user_id`分片。
  - 关系表按`user_id`分片。
- **异步化**：
  - 消息写入先入Kafka，异步落库，提高吞吐。
  - 推送任务异步处理，避免阻塞WebSocket服务器。
- **限流与降级**：
  - API网关对每个用户请求限流（如每秒10次）。
  - 突发流量时，丢弃非核心请求（如历史消息拉取），保证消息收发。

#### 4.8 安全性
- **传输加密**：全链路TLS（HTTPS/WSS）。
- **认证**：JWT令牌，短期有效，每次请求验证。
- **防篡改**：敏感操作（如修改密码）需二次验证。
- **内容安全**：图片/文件扫描，过滤违规内容。

### 5. 关键流程时序图（文字描述）

**一对一消息发送（在线）**  
1. A -> WebSocket服务器: 发送消息给B
2. WebSocket服务器 -> Kafka: 生产消息
3. Kafka消费者 -> 数据库: 写入消息，获取会话ID
4. 数据库返回成功
5. 消费者 -> Redis: 查询B的在线服务器节点
6. Redis返回节点ID
7. 消费者 -> WebSocket服务器B节点: 通过gRPC推送消息
8. WebSocket服务器B节点 -> B客户端: 推送消息
9. B客户端 -> WebSocket服务器B节点: 送达回执
10. WebSocket服务器B节点 -> 数据库: 更新消息状态

**用户上线拉取离线消息**  
1. B客户端 -> WebSocket服务器: 建立连接，发送online
2. WebSocket服务器 -> Redis: 记录B在线
3. B客户端 -> HTTP API: GET /messages/offline
4. API服务 -> 数据库: 查询B的所有会话及last_read_msg_id
5. 数据库返回会话列表
6. 对每个会话，查询message表获取大于last_read_msg_id的消息
7. 聚合消息返回给客户端
8. B客户端显示消息，并发送已读回执

### 6. 扩展性考虑
- **多端同步**：每个客户端独立维护last_read_msg_id，通过服务器同步消息状态。可使用版本向量或时间戳解决冲突。
- **文件存储**：图片/文件上传通过预签名URL直传对象存储，避免服务器中转。CDN加速分发。
- **推送通知**：当用户离线时，可通过APNS/FCM推送通知（移动端），Web端可使用Service Worker。
- **监控告警**：Prometheus + Grafana监控QPS、延迟、错误率；ELK收集日志。

### 7. 总结
本设计基于微服务+消息队列+缓存+分库分表，满足亿级用户的实时通信需求。通过读扩散存储消息、异步处理、水平扩展等机制保障高并发和低延迟。离线消息通过拉取模式实现，避免写放大。安全性和可靠性贯穿各层。实际部署时还需考虑容灾、多活等，可根据业务发展逐步优化。


```mermaid

graph TB
    subgraph 客户端
        A[浏览器/Web客户端]
    end

    subgraph 接入层
        B[CDN 静态资源加速]
        C[负载均衡 LVS/Nginx]
    end

    subgraph 网关层
        D[API网关<br>路由/认证/限流]
    end

    subgraph 业务服务层
        E[用户服务]
        F[关系服务]
        G[群组服务]
        H[消息服务]
        I[文件服务]
        J[推送服务]
    end

    subgraph 实时通信层
        K[WebSocket集群<br>长连接管理]
    end

    subgraph 中间件与数据层
        L[(Redis集群<br>缓存/在线状态)]
        M[(MySQL/TiDB<br>分库分表)]
        N[Kafka消息队列]
        O[(对象存储<br>MinIO/S3)]
    end

    subgraph 监控与日志
        P[Prometheus/Grafana<br>监控告警]
        Q[ELK 日志收集]
    end

    A -- HTTPS请求 --> C
    A -- 静态资源请求 --> B
    B -- 回源 --> O
    C -- HTTP API --> D
    C -- WebSocket握手 --> K
    D -- 调用 --> E
    D -- 调用 --> F
    D -- 调用 --> G
    D -- 调用 --> H
    D -- 调用 --> I
    D -- 调用 --> J
    K -- 内部RPC --> H
    K -- 读写 --> L
    H -- 读写 --> L
    H -- 写入消息 --> N
    H -- 持久化 --> M
    H -- 文件元数据 --> I
    I -- 上传/下载 --> O
    J -- 推送通知 --> 外部推送服务
    N -- 异步消费 --> H
    E --> M
    F --> M
    G --> M
    E --> L
    F --> L
    G --> L
    P -. 采集指标 .-> C
    P -. 采集指标 .-> D
    P -. 采集指标 .-> K
    P -. 采集指标 .-> 业务服务层
    Q -. 收集日志 .-> 所有服务
		```
