10万并发IM系统架构设计方案

10万并发IM系统架构设计方案

文档信息

项目 内容
文档标题 10万并发IM系统架构设计方案
技术负责人 架构师团队
创建日期 2026-01-17
更新日期 2026-01-17
版本号 V1.0
文档状态 正式版

一、业务需求概述

1.1 核心业务场景

构建一个支持10万并发在线用户的高性能IM即时通讯系统,满足以下核心场景:

业务场景说明:

  • 用户规模:平台支持10万用户同时在线
  • 消息类型:支持单聊、群聊、系统通知、实时状态同步
  • 典型分布:平均每个用户同时加入5-10个会话,活跃用户占比30%

核心功能:

  1. 单对单聊天:支持文本、图片、语音、视频消息的实时收发
  2. 群组聊天:支持群聊、讨论组,单群最高5000人
  3. 在线状态:实时同步用户在线/离线/忙碌等状态
  4. 消息推送:支持离线消息推送、未读消息提醒
  5. 消息漫游:支持消息历史记录的多端同步
  6. 断线重连:支持网络闪断后的自动重连和消息补发
  7. 消息可靠性:保证消息不丢失、不重复、有序送达

1.2 性能指标要求

指标项 目标值 说明
并发在线用户数 10万+ 支持10万用户同时保持长连接
消息延迟 < 200ms 点对点消息端到端延迟
群消息延迟 < 500ms 1000人群消息全员到达延迟
连接建立时间 < 1秒 用户登录到建立长连接的时间
系统可用性 99.99% 年停机时间不超过52.56分钟
消息送达率 99.99% 消息成功送达率
断线重连时间 < 3秒 网络恢复后自动重连时间
消息吞吐量 10万QPS 系统总消息处理能力

1.3 技术挑战

  1. 海量长连接:10万并发长连接的维持,每个连接占用内存和CPU资源
  2. 消息可靠性:网络不稳定情况下保证消息不丢失、不重复、有序
  3. 低延迟:毫秒级消息投递延迟要求
  4. 高可用:服务宕机不影响整体服务,快速故障转移
  5. 断线重连:网络闪断后快速重连,消息补发不遗漏
  6. 水平扩展:支持动态扩容,应对突发流量
  7. 状态同步:用户状态、会话状态的实时同步
  8. 消息存储:海量历史消息的存储和快速检索

二、架构核心理念

2.1 为什么选择长连接?

IM系统的本质需求:实时双向通信

短连接轮询 vs 长连接对比:

短连接轮询(HTTP):                   长连接(WebSocket/TCP):
┌────────────────────┐                ┌────────────────────┐
│   客户端            │                │   客户端            │
└─────┬──────────────┘                └─────┬──────────────┘
      │ 1秒一次轮询                           │ 一次连接
      │ GET /messages                        │ WebSocket握手
      ↓                                      ↓
┌────────────────────┐                ┌────────────────────┐
│   服务器            │                │   服务器            │
│ 返回:无新消息       │                │ 保持连接            │
└────────────────────┘                │ 有消息主动推送       │
                                      └────────────────────┘
每秒10万次请求                        10万个长连接
带宽:10万 × 1KB = 100MB/s           带宽:心跳包 ≈ 10KB/s
服务器压力:极大                      服务器压力:可控
实时性:差(1秒延迟)                 实时性:好(毫秒级)

结论:IM系统必须使用长连接

2.2 架构设计原则

  1. 接入层无状态化:接入服务器不保存用户状态,支持水平扩展
  2. 消息队列解耦:使用MQ解耦消息生产和消费,提高可靠性
  3. 状态集中管理:用户在线状态、连接映射关系集中存储在Redis
  4. 分层架构:接入层、逻辑层、存储层分离,职责清晰
  5. 读写分离:消息写入和消息读取分离,优化性能
  6. 多级缓存:热点数据多级缓存,减少数据库压力
  7. 异地多活:支持多机房部署,就近接入

三、整体架构设计

3.1 系统架构图

┌─────────────────────────────────────────────────────────────────────────┐
│                           10万并发IM系统架构                               │
└─────────────────────────────────────────────────────────────────────────┘

                         ┌──────────────┐
                         │   客户端      │
                         │ (10万用户)    │
                         └──────┬───────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
            ┌───────▼────────┐     ┌───────▼────────┐
            │  LVS负载均衡     │     │  DNS智能解析   │
            │ (4层负载均衡)    │     │  (就近接入)    │
            └───────┬────────┘     └───────┬────────┘
                    │                       │
        ┌───────────┴───────────────────────┴──────────┐
        │                                               │
┌───────▼────────┐  ┌──────────────┐  ┌──────────────┐
│  WebSocket      │  │  TCP长连接    │  │  HTTP短连接   │
│  接入服务器      │  │  接入服务器   │  │  接入服务器   │
│  (50台×2000)   │  │  (50台×2000) │  │  (备用方案)   │
└───────┬────────┘  └──────┬───────┘  └──────┬───────┘
        │                  │                  │
        └──────────────────┼──────────────────┘
                           │
                    ┌──────▼───────┐
                    │  Nginx/网关   │
                    │  (API网关)    │
                    └──────┬───────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
┌───────▼────────┐  ┌──────▼───────┐  ┌──────▼───────┐
│  业务逻辑层      │  │  消息路由层   │  │  推送服务层   │
│ (无状态服务)    │  │  (消息分发)   │  │  (离线推送)   │
└───────┬────────┘  └──────┬───────┘  └──────┬───────┘
        │                  │                  │
        └──────────────────┼──────────────────┘
                           │
        ┌──────────────────┼──────────────────┐
        │                  │                  │
┌───────▼────────┐  ┌──────▼───────┐  ┌──────▼───────┐
│   Redis集群     │  │  Kafka消息队列│  │  MongoDB     │
│ (状态+缓存)     │  │  (消息中转)   │  │  (消息存储)   │
└────────────────┘  └──────────────┘  └──────────────┘
        │                  │                  │
        └──────────────────┼──────────────────┘
                           │
                    ┌──────▼───────┐
                    │  MySQL主从    │
                    │  (关系数据)   │
                    └──────────────┘

3.2 核心组件说明

3.2.1 接入层(Gateway Layer)

职责:

  • 维持10万用户的WebSocket/TCP长连接
  • 处理连接建立、心跳保活、断线重连
  • 消息编解码、协议转换
  • 连接状态上报

技术选型:

  • WebSocket:Web端、H5端首选,浏览器原生支持
  • TCP:移动端(iOS/Android)首选,性能更好
  • 服务器:Netty(Java)、Go(高性能)

容量规划:

  • 单台服务器:2000并发连接(经验值)
  • 总需求:10万连接 ÷ 2000 = 50台服务器
  • 冗余:考虑30%冗余,实际部署65台
  • 内存:每连接10KB,2000连接≈20MB,单机4GB内存足够

3.2.2 业务逻辑层(Business Layer)

职责:

  • 用户认证、权限校验
  • 消息合法性校验、敏感词过滤
  • 好友关系、群组关系管理
  • 消息已读状态、会话管理

技术选型:

  • Spring Boot微服务集群
  • Dubbo/gRPC服务调用
  • 无状态设计,支持水平扩展

3.2.3 消息路由层(Router Layer)

职责:

  • 根据用户ID路由到对应的接入服务器
  • 消息分发(单聊、群聊、广播)
  • 维护用户ID到接入服务器的映射关系

核心数据结构(Redis):

# 用户在线状态
user:online:{userId} → {serverId, timestamp, deviceId}

# 服务器连接映射
server:connections:{serverId} → Set[userId1, userId2, ...]

# 群组成员映射
group:members:{groupId} → Set[userId1, userId2, ...]

3.2.4 消息队列(Message Queue)

职责:

  • 消息异步处理,削峰填谷
  • 解耦消息生产者和消费者
  • 保证消息可靠性(持久化)

技术选型:

  • Kafka:高吞吐量,支持消息持久化和回溯
  • RocketMQ:阿里开源,消息可靠性更强

Topic设计:

im-message-p2p      # 单聊消息
im-message-group    # 群聊消息
im-message-system   # 系统消息
im-message-offline  # 离线消息

3.2.5 存储层(Storage Layer)

MongoDB(消息存储):

  • 存储所有历史消息
  • 按会话ID分片,支持快速检索
  • 冷热数据分离,历史消息归档

Redis(缓存+状态):

  • 用户在线状态
  • 最近消息缓存(最近100条)
  • 会话列表缓存
  • 未读消息计数

MySQL(关系数据):

  • 用户信息、好友关系
  • 群组信息、群成员关系
  • 用户配置、黑名单

四、核心功能设计

4.1 长连接管理

4.1.1 连接建立流程

客户端                接入服务器           业务服务器           Redis
  │                      │                   │                  │
  │  1. WebSocket握手    │                   │                  │
  │ ─────────────────> │                   │                  │
  │                      │                   │                  │
  │  2. 发送认证信息      │                   │                  │
  │  {token, deviceId} │                   │                  │
  │ ─────────────────> │                   │                  │
  │                      │  3. 验证Token      │                  │
  │                      │ ──────────────> │                  │
  │                      │                   │                  │
  │                      │  4. Token有效      │                  │
  │                      │ <────────────── │                  │
  │                      │                   │                  │
  │                      │  5. 上报在线状态                      │
  │                      │  userId→serverId                     │
  │                      │ ─────────────────────────────────> │
  │                      │                                     │
  │  6. 连接成功响应      │                   6. 查询离线消息    │
  │ <───────────────── │ <─────────────────────────────────│
  │                      │                                     │
  │  7. 推送离线消息      │                                     │
  │ <───────────────── │                                     │
  │                      │                                     │

关键点:

  1. 认证在建立连接后:先握手,后认证,避免无效连接占用资源
  2. 上报在线状态:Redis记录userId → serverId映射
  3. 离线消息推送:连接成功后立即推送离线消息

4.1.2 心跳保活机制

为什么需要心跳?

  • 检测连接是否存活(网络中断、客户端崩溃)
  • NAT穿透,防止路由器/防火墙关闭长连接
  • 及时清理僵尸连接,释放服务器资源

心跳策略:

客户端心跳:每30秒发送一次心跳包
服务器超时:90秒未收到心跳,主动断开连接

客户端                      服务器
  │                          │
  │ ─── PING ──────────────>│  (30秒)
  │                          │
  │ <── PONG ───────────────│  (立即响应)
  │                          │
  │ ─── PING ──────────────>│  (60秒)
  │                          │
  │ <── PONG ───────────────│  (立即响应)
  │                          │
  │  (网络中断)               │
  │                          │  (90秒超时)
  │                          │  触发断开连接
  │                          │  清理连接状态

优化:

  • 自适应心跳:网络良好时延长心跳间隔(60秒),节省流量
  • 智能心跳:前台活跃时30秒,后台时60秒

4.2 消息收发流程

4.2.1 单聊消息流程

发送方                接入服务器A         消息队列           接入服务器B          接收方
  │                      │                  │                  │                 │
  │ 1. 发送消息          │                  │                  │                 │
  │ {to, content, msgId}│                  │                  │                 │
  │ ─────────────────> │                  │                  │                 │
  │                      │                  │                  │                 │
  │ 2. ACK响应(已收到)   │  3. 投递到Kafka   │                  │                 │
  │ <───────────────── │ ──────────────> │                  │                 │
  │                      │                  │                  │                 │
  │                      │                  │  4. 消费消息      │                 │
  │                      │                  │  查询接收方在线   │                 │
  │                      │                  │ ──────────────> │                 │
  │                      │                  │                  │                 │
  │                      │                  │                  │  5. 推送消息     │
  │                      │                  │                  │ ──────────────>│
  │                      │                  │                  │                 │
  │                      │                  │                  │  6. ACK响应      │
  │ 7. 送达通知          │ <──────────────────────────────────│ <──────────────│
  │ <───────────────── │                  │                  │                 │
  │                      │                  │                  │                 │

消息ID生成:

  • 雪花算法(Snowflake):保证全局唯一、递增
  • 格式:时间戳(41位) + 机器ID(10位) + 序列号(12位)

消息状态流转:

[已发送] → [已送达] → [已读]
   ↓
[发送失败](重试3次)

4.2.2 群聊消息流程

发送方             接入服务器         消息队列           群消息分发服务        接入服务器集群
  │                   │                 │                   │                    │
  │ 1. 发送群消息     │                 │                   │                    │
  │ {groupId,content}│                 │                   │                    │
  │ ────────────────>│                 │                   │                    │
  │                   │                 │                   │                    │
  │ 2. ACK           │ 3. 投递Kafka     │                   │                    │
  │ <────────────────│ ──────────────> │                   │                    │
  │                   │                 │                   │                    │
  │                   │                 │  4. 消费消息       │                    │
  │                   │                 │  查询群成员列表    │                    │
  │                   │                 │ ────────────────> │                    │
  │                   │                 │                   │                    │
  │                   │                 │                   │ 5. 批量推送(扇出)  │
  │                   │                 │                   │ ─────────────────>│
  │                   │                 │                   │   50台服务器        │
  │                   │                 │                   │   每台40人         │
  │                   │                 │                   │                    │

群消息优化:

  1. 扇出写:一条消息写入,多次读取(每个群成员一次)
  2. 异步推送:避免阻塞发送方
  3. 限流保护:大群(1000人+)消息限流,防止雪崩
  4. 离线合并:离线用户消息合并推送,减少推送次数

大群优化策略:

小群(<100人)    :实时推送每个成员
中群(100-1000人):实时推送在线成员,离线成员合并推送
大群(>1000人)   :批量推送,分批次,延迟容忍度更高

4.3 断线重连机制

4.3.1 断线检测

断线场景:

  1. 客户端主动断开:用户退出、杀进程、切换网络
  2. 网络闪断:电梯、地铁、WIFI切换4G
  3. 服务器主动断开:心跳超时、服务升级
  4. 中间设备断开:NAT超时、防火墙策略

检测机制:

客户端检测:
- 发送消息失败
- 心跳发送失败
- TCP连接异常(FIN/RST)
- WebSocket onClose事件

服务器检测:
- 90秒未收到心跳
- TCP读写异常
- 操作系统通知连接关闭

4.3.2 重连策略

指数退避重连:

第1次:立即重连(0秒)
第2次:2秒后重连
第3次:4秒后重连
第4次:8秒后重连
第5次:16秒后重连
第6次及以上:30秒后重连(上限)

最大重连次数:无限次(用户主动停止前)

重连流程:

客户端                接入服务器             Redis
  │                      │                    │
  │  检测到断线           │                    │
  │  ┌────────┐          │                    │
  │  │指数退避 │          │                    │
  │  └───┬────┘          │                    │
  │      │               │                    │
  │  1. 重新建立连接      │                    │
  │ ─────────────────>  │                    │
  │                      │                    │
  │  2. 发送认证信息      │                    │
  │  {token, lastMsgId} │                    │
  │ ─────────────────>  │                    │
  │                      │  3. 验证Token       │
  │                      │  查询离线期间消息    │
  │                      │ ─────────────────> │
  │                      │                    │
  │  4. 连接成功          │  5. 补发消息        │
  │ <───────────────── │ <─────────────────│
  │                      │                    │
  │  6. 推送断线期间消息  │                    │
  │ <───────────────── │                    │
  │                      │                    │

关键点:

  1. 携带lastMsgId:客户端记录最后收到的消息ID,重连后从该位置补发
  2. 去重处理:服务器根据msgId去重,避免重复推送
  3. 消息缓冲:Redis缓存最近1小时的消息,快速补发

4.3.3 消息补发机制

补发策略:

场景1:短时间断线(<5分钟)
- 从Redis缓存中补发(O(1)查询)
- 最多补发500条消息

场景2:长时间断线(>5分钟)
- 从MongoDB查询历史消息
- 最多补发最近1000条
- 超过1000条,提示用户"消息过多,请查看历史记录"

场景3:多端同步
- 用户在A设备离线,B设备在线收消息
- A设备重连后,从lastMsgId同步

补发伪代码逻辑:

function reconnect(userId, lastMsgId) {
    // 1. 验证用户身份
    if (!validateToken()) {
        return ERROR;
    }
    
    // 2. 更新在线状态
    updateOnlineStatus(userId, serverId);
    
    // 3. 计算需要补发的消息
    List<Message> missedMessages = [];
    
    // 优先从Redis缓存查询
    missedMessages = redis.getMessagesAfter(userId, lastMsgId);
    
    // 如果Redis没有,从MongoDB查询
    if (missedMessages.isEmpty()) {
        missedMessages = mongodb.getMessagesAfter(userId, lastMsgId, limit=1000);
    }
    
    // 4. 去重(客户端可能已收到部分消息)
    missedMessages = deduplicateByMsgId(missedMessages);
    
    // 5. 批量推送
    for (message in missedMessages) {
        pushMessage(userId, message);
    }
    
    // 6. 返回重连成功
    return SUCCESS;
}

五、高可用设计

5.1 服务高可用

5.1.1 接入层高可用

问题:接入服务器宕机,10万连接如何保障?

方案:主动健康检查 + 快速故障转移

             LVS负载均衡
                 │
        ┌────────┼────────┐
        │        │        │
    ┌───▼───┐┌───▼───┐┌───▼───┐
    │Server1││Server2││Server3│
    │ 正常  ││ 宕机  ││ 正常  │
    │2000连 ││   0   ││2000连 │
    └───────┘└───┬───┘└───────┘
                 │
            健康检查失败
                 │
            LVS摘除节点
                 │
         ┌───────▼────────┐
         │  断开2000个连接 │
         └───────┬────────┘
                 │
         ┌───────▼────────┐
         │ 客户端检测到断线│
         │  自动重连到其他 │
         │    健康节点     │
         └────────────────┘

容灾措施:

  1. 健康检查:LVS每3秒检查一次,连续3次失败摘除节点
  2. 冗余部署:实际部署65台(10万÷2000×1.3冗余)
  3. 故障转移时间:检测(9秒)+ 重连(3秒)= 12秒内恢复
  4. 故障影响面:单台宕机影响2000用户,占比2%

5.1.2 业务层高可用

方案:微服务 + 服务注册发现

┌─────────────────────────────────────┐
│         Nacos/Consul                │
│      (服务注册与发现中心)             │
└──────────┬──────────────────────────┘
           │
    ┌──────┼──────┐
    │      │      │
┌───▼──┐┌──▼──┐┌──▼──┐
│服务A ││服务B││服务C│
│实例1 ││实例1││实例1│
└──────┘└─────┘└─────┘
┌───▼──┐┌──▼──┐┌──▼──┐
│服务A ││服务B││服务C│
│实例2 ││实例2││实例2│
└──────┘└─────┘└─────┘
┌───▼──┐┌──▼──┐┌──▼──┐
│服务A ││服务B││服务C│
│实例3 ││实例3││实例3│
└──────┘└─────┘└─────┘

服务自动注册、心跳上报
故障实例自动摘除
客户端负载均衡

关键配置:

  • 心跳间隔:5秒
  • 超时时间:15秒(3次心跳)
  • 最小实例数:每个服务最少3个实例
  • 熔断降级:Hystrix/Sentinel熔断保护

5.1.3 存储层高可用

Redis高可用:Redis Cluster + 哨兵

┌──────────────────────────────────────┐
│         Redis Cluster                │
│   (16384个槽位,分片存储)             │
└──────────────────────────────────────┘

    Master1        Master2        Master3
   (槽位0-5460)  (槽位5461-10922) (槽位10923-16383)
       │             │              │
    Slave1         Slave2         Slave3
   (主从复制)     (主从复制)      (主从复制)

哨兵集群(Sentinel):
- 监控Master健康
- Master故障自动提升Slave为Master
- 故障转移时间:< 30秒

MongoDB高可用:副本集(Replica Set)

       Primary
         │
    ┌────┴────┐
Secondary  Secondary
(异步复制) (异步复制)

写入:只写Primary
读取:可读Secondary(最终一致性)
故障转移:Secondary自动选举为Primary

MySQL高可用:主从复制 + MHA

       Master(写)
         │
    ┌────┴────┐
 Slave1(读) Slave2(读)

MHA(Master High Availability):
- 监控Master健康
- Master故障,Slave提升为Master
- 故障转移时间:< 30秒

5.2 消息可靠性保障

5.2.1 消息ACK机制

三次握手保证消息可靠:

发送方              接入服务器            Kafka             接收方
  │                    │                   │                  │
  │ 1. 发送消息        │                   │                  │
  │ {msgId, content}  │                   │                  │
  │ ─────────────────>│                   │                  │
  │                    │  2. 持久化到Kafka  │                  │
  │                    │ ────────────────> │                  │
  │                    │                   │                  │
  │                    │  3. Kafka ACK     │                  │
  │                    │ <──────────────── │                  │
  │                    │                   │                  │
  │  4. ACK1(已收到)   │                   │  5. 推送消息      │
  │ <─────────────────│                   │ ──────────────> │
  │                    │                   │                  │
  │                    │                   │  6. ACK2(已送达) │
  │  7. 送达通知       │ <──────────────────────────────────│
  │ <─────────────────│                   │                  │
  │                    │                   │                  │

ACK状态:

  • ACK1(已收到):服务器收到消息,已持久化
  • ACK2(已送达):接收方客户端收到消息
  • ACK3(已读):接收方已读消息(可选)

5.2.2 消息去重

问题:网络重传、重连补发导致消息重复

去重策略:

  1. 客户端去重:根据msgId去重
// 客户端维护已收消息集合(LRU缓存,最近1000条)
Set<String> receivedMsgIds = new LRUCache<>(1000);

function onReceiveMessage(message) {
    if (receivedMsgIds.contains(message.msgId)) {
        // 重复消息,忽略
        return;
    }
    receivedMsgIds.add(message.msgId);
    // 处理消息
    displayMessage(message);
}
  1. 服务端去重:Redis记录已送达消息
# 记录已送达消息(7天过期)
msg:delivered:{userId}:{msgId} → timestamp
TTL: 7天

5.2.3 消息有序性

问题:网络乱序、并发推送导致消息顺序错乱

有序性保证:

  1. 单聊消息有序
Kafka分区策略:hash(senderId + receiverId) % partition_num
同一对用户的消息落到同一分区,保证有序
  1. 群聊消息有序
Kafka分区策略:hash(groupId) % partition_num
同一个群的消息落到同一分区,保证有序
  1. 客户端排序
// 客户端根据消息序列号(seqId)排序
List<Message> messages = ...;
messages.sort((m1, m2) -> m1.seqId - m2.seqId);

5.3 限流与降级

5.3.1 限流策略

限流目的:防止突发流量打垮系统

限流维度:

  1. 用户级限流
单用户发消息频率:10条/秒(令牌桶算法)
单用户群发消息:5条/秒

Redis计数器:
rate:limit:user:{userId} → count
TTL: 1秒
  1. 群组级限流
小群(<100人)  :100条/秒
中群(100-1000):50条/秒
大群(>1000)   :20条/秒
  1. 全局限流
接入服务器:每台2000连接,超过拒绝新连接
消息队列:10万QPS,超过返回系统繁忙

5.3.2 降级策略

降级场景:系统负载过高、依赖服务故障

降级措施:

优先级 功能 降级策略
P0 单聊消息 保持服务
P1 群聊消息(小群) 保持服务
P2 群聊消息(大群) 延迟推送
P3 在线状态 停止同步
P4 消息已读回执 停止发送
P5 历史消息查询 返回"系统繁忙"

六、性能优化

6.1 连接优化

6.1.1 连接复用

问题:频繁建立TCP连接开销大(三次握手)

方案:长连接复用

短连接:                      长连接:
每次请求新建连接               一次连接,多次请求
│                            │
├─ 建立连接(3次握手)        ├─ 建立连接(3次握手)
├─ 发送请求1                  ├─ 发送请求1
├─ 关闭连接(4次挥手)        ├─ 发送请求2
├─ 建立连接(3次握手)        ├─ 发送请求3
├─ 发送请求2                  │  ...
└─ 关闭连接(4次挥手)        └─ 保持连接

开销:N次握手挥手             开销:1次握手

6.1.2 连接池

服务端连接池:

数据库连接池:HikariCP
- 最小连接数:10
- 最大连接数:100
- 连接超时:30秒

Redis连接池:Jedis/Lettuce
- 最小连接数:5
- 最大连接数:50
- 连接超时:5秒

6.2 消息优化

6.2.1 消息压缩

场景:长文本消息、群聊消息

原始消息:1KB
压缩后:300B(压缩率70%)

压缩算法:Gzip、Snappy
压缩策略:消息>512B自动压缩

6.2.2 批量推送

场景:群聊消息、系统通知

单条推送:                    批量推送:
for (user in users) {        List<User> batch = [];
    push(user, message);     for (user in users) {
}                                batch.add(user);
                                 if (batch.size() >= 100) {
耗时:1000人×10ms=10秒             batchPush(batch, message);
                                     batch.clear();
                                 }
                             }
                             
                             耗时:1000人÷100×50ms=0.5秒

6.3 缓存优化

6.3.1 多级缓存

                请求流程
                   │
         ┌─────────▼──────────┐
         │   本地缓存(L1)      │ ← Caffeine/Guava Cache
         │   命中率:80%       │    容量:10000条
         │   延迟:1ms         │    过期:5分钟
         └─────────┬──────────┘
                   │ Miss
         ┌─────────▼──────────┐
         │  Redis缓存(L2)     │ ← Redis Cluster
         │   命中率:95%       │    容量:100万条
         │   延迟:5ms         │    过期:1小时
         └─────────┬──────────┘
                   │ Miss
         ┌─────────▼──────────┐
         │  数据库(DB)        │ ← MongoDB/MySQL
         │   命中率:100%      │    持久化存储
         │   延迟:50ms        │    无过期
         └────────────────────┘

总命中率:80% + 20%×95% = 99%
平均延迟:0.8×1ms + 0.19×5ms + 0.01×50ms = 2.25ms

6.3.2 热点数据识别

策略:

热点群组:访问频率>100次/分钟
热点用户:访问频率>500次/分钟

热点数据:
- 提升缓存优先级(不过期)
- 增加缓存副本(多节点)
- 本地缓存预热

6.4 数据库优化

6.4.1 分库分表

MongoDB分片策略:

消息表按会话ID分片:
shard1: conversationId % 10 = 0
shard2: conversationId % 10 = 1
...
shard10: conversationId % 10 = 9

单表数据量:1000万条(控制在合理范围)
查询性能:O(1)定位到分片,索引查询

MySQL分库分表:

用户表按userId分库:
db1: userId % 4 = 0
db2: userId % 4 = 1
db3: userId % 4 = 2
db4: userId % 4 = 3

每个库内按userId分表:
user_0: userId % 10 = 0
user_1: userId % 10 = 1
...
user_9: userId % 10 = 9

6.4.2 索引优化

MongoDB索引:

// 消息表索引
db.messages.createIndex({
    conversationId: 1,    // 会话ID
    timestamp: -1         // 时间倒序
});

// 查询最近消息
db.messages.find({
    conversationId: "xxx"
}).sort({timestamp: -1}).limit(100);

// 索引覆盖,无需回表

MySQL索引:

-- 用户表索引
CREATE INDEX idx_user_status ON user(status, last_login_time);

-- 好友关系表联合索引
CREATE INDEX idx_friend_relation ON friend(user_id, friend_id, status);

七、监控与运维

7.1 监控指标

7.1.1 核心业务指标

指标 说明 告警阈值
在线用户数 实时在线用户总数
消息发送量 每秒发送消息数(QPS) >8万告警
消息延迟 消息端到端延迟(P99) >500ms告警
连接成功率 用户连接成功率 <98%告警
消息送达率 消息成功送达率 <99.9%告警
重连次数 每分钟重连次数 >1000告警

7.1.2 系统资源指标

指标 说明 告警阈值
CPU使用率 服务器CPU使用率 >80%告警
内存使用率 服务器内存使用率 >85%告警
网络带宽 入站/出站带宽使用率 >80%告警
连接数 单机TCP连接数 >1800告警
磁盘IO 磁盘读写IOPS >80%告警

7.1.3 中间件指标

中间件 监控指标 告警阈值
Redis 内存使用率、QPS、慢查询 >85%、>10万、>100ms
Kafka 消息堆积、消费延迟 >10万条、>5秒
MongoDB QPS、慢查询、连接数 >2万、>100ms、>500
MySQL QPS、慢查询、主从延迟 >5000、>1秒、>5秒

7.2 日志与追踪

7.2.1 日志规范

日志级别:

ERROR:系统错误,需要人工介入
WARN :业务异常,需要关注
INFO :关键流程日志
DEBUG:调试日志(生产环境关闭)

日志内容:

{
    "timestamp": "2026-01-17T10:30:25.123Z",
    "level": "INFO",
    "traceId": "1234567890abcdef",  // 全链路追踪ID
    "userId": "1001",
    "action": "send_message",
    "msgId": "msg_1234567890",
    "cost": "25ms",
    "status": "success"
}

7.2.2 全链路追踪

OpenTracing/SkyWalking:

客户端           接入服务器        业务服务器        消息队列         接收方
  │                 │                 │                │              │
  │ TraceId生成     │                 │                │              │
  │ ──────────────>│ SpanId=1        │                │              │
  │                 │ ──────────────>│ SpanId=2       │              │
  │                 │                 │ ──────────────>│ SpanId=3     │
  │                 │                 │                │ ───────────> │ SpanId=4
  │                 │                 │                │              │

TraceId: 唯一标识一次完整请求
SpanId: 标识请求的每个阶段
通过TraceId可以追踪消息全链路耗时

7.3 容量规划

7.3.1 服务器容量

接入服务器:

单机容量:2000并发连接
当前需求:10万连接
理论数量:10万 ÷ 2000 = 50台
实际部署:50 × 1.3(冗余)= 65台

配置:4核8G,千兆网卡
成本:500元/台/月 × 65台 = 3.25万元/月

业务服务器:

单机QPS:1000
当前需求:10万QPS
理论数量:10万 ÷ 1000 = 100台
实际部署:100 × 1.2(冗余)= 120台

配置:8核16G
成本:800元/台/月 × 120台 = 9.6万元/月

7.3.2 存储容量

Redis集群:

单用户状态:1KB
在线用户:10万
内存需求:10万 × 1KB = 100MB

消息缓存(最近1小时):
单条消息:1KB
消息量:10万用户 × 5条/分钟 × 60分钟 = 3000万条
内存需求:3000万 × 1KB = 30GB

总内存:100MB + 30GB ≈ 30GB
冗余:30GB × 3(主从)× 1.5(冗余)= 135GB

部署:3主3从 × 32GB = 192GB
成本:2000元/月/32GB × 6台 = 1.2万元/月

MongoDB集群:

单条消息:1KB
日消息量:10万用户 × 100条/天 = 1000万条/天
日存储:1000万 × 1KB = 10GB/天
年存储:10GB × 365天 = 3.65TB

分片策略:10个分片
单分片:365GB/年

部署:10分片 × 3副本 × 500GB = 15TB
成本:0.5元/GB/月 × 15000GB = 7500元/月

7.3.3 网络带宽

入站带宽(接收消息):

单条消息:1KB
消息频率:10万用户 × 5条/分钟 = 50万条/分钟 ≈ 8333条/秒
带宽需求:8333条 × 1KB = 8.3MB/s ≈ 67Mbps

出站带宽(推送消息):

单条消息:1KB
消息频率:8333条/秒
平均每条消息推送给2个用户(单聊)
带宽需求:8333 × 2 × 1KB = 16.7MB/s ≈ 134Mbps

群聊扩散(假设20%是群聊,平均10人/群):
群聊带宽:8333 × 0.2 × 10 × 1KB = 16.7MB/s ≈ 134Mbps

总带宽:134Mbps + 134Mbps = 268Mbps ≈ 300Mbps

总成本估算:

接入服务器:3.25万元/月
业务服务器:9.6万元/月
Redis集群:1.2万元/月
MongoDB集群:0.75万元/月
网络带宽:300Mbps × 50元/Mbps = 1.5万元/月

合计:16.3万元/月 ≈ 200万元/年

八、安全设计

8.1 认证与授权

8.1.1 用户认证

Token机制:

1. 用户登录 → 业务服务器验证账号密码
2. 验证成功 → 生成JWT Token(有效期7天)
3. 客户端保存Token
4. 建立长连接时携带Token
5. 接入服务器验证Token(Redis缓存验证结果)

Token格式:
{
    "userId": "1001",
    "deviceId": "iPhone12_xxxx",
    "exp": 1736985600,  // 过期时间
    "signature": "xxx"   // 签名
}

8.1.2 消息加密

传输层加密:

WebSocket:WSS(WebSocket over TLS/SSL)
TCP:TLS 1.3

加密算法:AES-256
证书:Let's Encrypt免费证书

端到端加密(可选):

场景:私密聊天
算法:RSA + AES混合加密
流程:
1. 双方交换公钥
2. 发送方用接收方公钥加密AES密钥
3. 用AES密钥加密消息内容
4. 服务器只转发密文,无法解密

8.2 防攻击

8.2.1 DDoS防护

措施:

  1. 接入层限流:单IP连接数限制(10个/IP)
  2. 云服务DDoS防护:阿里云DDoS高防
  3. 黑名单:自动封禁异常IP

8.2.2 防刷保护

消息防刷:

限制策略:
- 单用户:10条/秒
- 单IP:100条/秒
- 相同内容:3条/分钟(防止复制粘贴刷屏)

惩罚措施:
- 触发1次:警告
- 触发3次:禁言10分钟
- 触发10次:封号24小时

8.2.3 敏感词过滤

方案:DFA算法(确定有限状态自动机)

敏感词库:10万词
过滤性能:O(n),n为消息长度
处理时间:<10ms

命中策略:
- 直接拦截:涉政、涉黄
- 替换处理:脏话、辱骂(替换为***)
- 人工审核:疑似违规(异步审核)

九、扩展性设计

9.1 水平扩展

9.1.1 无状态设计

接入层无状态:

✗ 错误:连接信息存储在接入服务器内存
- 服务器宕机,连接信息丢失
- 无法水平扩展

✓ 正确:连接信息存储在Redis
- 任意接入服务器都可查询
- 服务器可以随意增减
- 支持弹性伸缩

9.1.2 分片策略

Redis分片:

一致性哈希:hash(userId) → Redis节点
虚拟节点:每个物理节点映射150个虚拟节点

优势:
- 增删节点只影响相邻节点
- 数据迁移量小

Kafka分区:

分区数:100个分区
扩展方式:增加消费者实例(最多100个)

注意:分区数一旦确定不建议修改
初始规划:按峰值3倍设计

9.2 多机房部署

9.2.1 就近接入

┌─────────────────────────────────────────┐
│          DNS智能解析                     │
│      (GeoDNS就近接入)                    │
└──────────┬──────────────────────────────┘
           │
    ┌──────┴──────┐
    │             │
┌───▼───┐    ┌────▼────┐
│ 北京机房│    │ 深圳机房 │
│ 5万用户 │    │ 5万用户  │
└───┬───┘    └────┬────┘
    │             │
    └──────┬──────┘
           │
    ┌──────▼──────┐
    │  消息同步    │
    │ (Kafka同步) │
    └─────────────┘

跨机房消息同步:

同机房:延迟<50ms
跨机房:延迟100-200ms

同步方案:
- Kafka Mirror Maker 2.0
- 消息异步复制(最终一致性)

十、总结与展望

10.1 方案总结

本方案设计了一个支持10万并发在线用户的高性能IM系统,核心特点:

  1. 高并发:单机2000连接,65台服务器支撑10万并发
  2. 高可用:多层冗余,故障自动转移,可用性99.99%
  3. 低延迟:消息端到端延迟<200ms,群消息<500ms
  4. 高可靠:三重ACK保证消息不丢失,去重保证不重复
  5. 断线重连:指数退避重连,3秒内恢复,消息自动补发
  6. 可扩展:无状态设计,支持水平扩展,弹性伸缩

10.2 技术栈总结

层级 技术选型 说明
接入层 Netty/Go WebSocket 长连接管理
网关层 Nginx/OpenResty 负载均衡、限流
业务层 Spring Boot/Dubbo 微服务架构
消息队列 Kafka 消息异步处理
缓存 Redis Cluster 状态存储、缓存
数据库 MongoDB + MySQL 消息存储、关系数据
注册中心 Nacos/Consul 服务发现
监控 Prometheus + Grafana 系统监控
追踪 SkyWalking 全链路追踪
负载均衡 LVS 四层负载均衡

10.3 关键指标达成

指标 目标 方案 达成情况
并发连接 10万 65台×2000连接 ✓ 达成
消息延迟 <200ms 优化推送链路 ✓ 达成
可用性 99.99% 多层冗余 ✓ 达成
消息可靠性 99.99% 三重ACK ✓ 达成
重连时间 <3秒 指数退避 ✓ 达成

10.4 成本预估

服务器成本:12.85万元/月
存储成本:1.95万元/月
网络成本:1.5万元/月

合计:16.3万元/月 ≈ 196万元/年

人均成本:196万 ÷ 10万用户 = 19.6元/人/年

10.5 未来优化方向

  1. 智能路由:AI预测用户活跃时段,提前预热连接
  2. 边缘计算:CDN边缘节点部署IM服务,进一步降低延迟
  3. 音视频通话:集成WebRTC,支持实时音视频
  4. 消息检索:ElasticSearch全文检索,快速查找历史消息
  5. 智能推荐:基于用户行为的好友推荐、群组推荐
  6. 消息翻译:多语言实时翻译,支持跨语言沟通

附录

附录A:核心API设计

A.1 连接认证API

请求:
POST /im/connect
Header: {
    "Authorization": "Bearer {token}"
}
Body: {
    "deviceId": "iPhone12_xxxx",
    "clientVersion": "1.0.0"
}

响应:
{
    "code": 200,
    "message": "success",
    "data": {
        "wsUrl": "wss://im.example.com:443",
        "serverId": "server-01",
        "heartbeatInterval": 30
    }
}

A.2 发送消息API

WebSocket消息格式:
{
    "cmd": "send_message",
    "msgId": "msg_1234567890",
    "from": "1001",
    "to": "1002",
    "type": "text",
    "content": "Hello",
    "timestamp": 1736985600
}

服务器ACK响应:
{
    "cmd": "ack",
    "msgId": "msg_1234567890",
    "status": "received",
    "timestamp": 1736985601
}

A.3 心跳API

客户端心跳:
{
    "cmd": "ping",
    "timestamp": 1736985600
}

服务器响应:
{
    "cmd": "pong",
    "timestamp": 1736985600
}

附录B:数据库表设计

B.1 用户表(MySQL)

CREATE TABLE `user` (
    `id` BIGINT PRIMARY KEY,
    `username` VARCHAR(64) NOT NULL,
    `password` VARCHAR(128) NOT NULL,
    `nickname` VARCHAR(64),
    `avatar` VARCHAR(255),
    `status` TINYINT DEFAULT 1, -- 1:正常 2:禁用
    `create_time` DATETIME,
    `update_time` DATETIME,
    INDEX idx_username(username),
    INDEX idx_status(status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

B.2 消息表(MongoDB)

{
    "_id": "msg_1234567890",
    "conversationId": "conv_1001_1002", // 会话ID
    "fromUserId": "1001",
    "toUserId": "1002",
    "msgType": "text", // text/image/voice/video
    "content": "Hello",
    "status": "delivered", // sent/delivered/read
    "timestamp": ISODate("2026-01-17T10:30:25.123Z"),
    "createTime": ISODate("2026-01-17T10:30:25.123Z")
}

// 索引
db.messages.createIndex({conversationId: 1, timestamp: -1});
db.messages.createIndex({fromUserId: 1, timestamp: -1});
db.messages.createIndex({toUserId: 1, timestamp: -1});

B.3 会话表(MongoDB)

{
    "_id": "conv_1001_1002",
    "type": "p2p", // p2p/group
    "members": ["1001", "1002"],
    "lastMessage": {
        "msgId": "msg_1234567890",
        "content": "Hello",
        "timestamp": ISODate("2026-01-17T10:30:25.123Z")
    },
    "unreadCount": {
        "1001": 0,
        "1002": 3
    },
    "createTime": ISODate("2026-01-17T10:30:25.123Z"),
    "updateTime": ISODate("2026-01-17T10:30:25.123Z")
}

附录C:协议设计

C.1 消息协议格式

┌─────────────────────────────────────┐
│         消息协议(二进制)            │
└─────────────────────────────────────┘

[魔数][版本][命令][长度][消息ID][消息体]
  2B    1B   1B    4B     8B      变长

魔数:0xCAFE(固定,用于识别协议)
版本:0x01(协议版本号)
命令:0x01=心跳 0x02=消息 0x03=ACK
长度:消息体长度(字节)
消息ID:唯一标识(雪花算法)
消息体:JSON格式或Protobuf

C.2 命令类型

0x01: PING      - 心跳请求
0x02: PONG      - 心跳响应
0x03: MESSAGE   - 消息
0x04: ACK       - 确认
0x05: LOGIN     - 登录
0x06: LOGOUT    - 登出
0x07: TYPING    - 正在输入
0x08: READ      - 已读

附录D:FAQ

Q1: 为什么选择WebSocket而不是HTTP长轮询?

A: WebSocket是全双工通信,服务器可以主动推送消息,实时性更好;HTTP长轮询需要客户端不断发起请求,服务器压力大,实时性差。

Q2: 如何保证消息的严格有序?

A: 通过Kafka分区保证同一对话的消息落到同一分区,分区内消息有序;客户端根据消息序列号(seqId)排序,双重保证。

Q3: 断线重连后如何避免消息重复?

A: 客户端维护已收消息ID集合(LRU缓存),服务器推送消息时,客户端根据msgId去重。

Q4: 如何应对突发流量(如明星进入群聊)?

A: 1)限流保护:群消息限流20条/秒;2)降级策略:延迟推送非关键消息;3)弹性扩容:自动扩容服务器。

Q5: 如何保证10万连接的稳定性?

A: 1)接入层冗余:实际部署65台(30%冗余);2)健康检查:LVS自动摘除故障节点;3)故障转移:客户端自动重连到健康节点。


文档结束

如有疑问,请联系架构师团队。

posted @ 2026-01-17 14:36  菜鸟~风  阅读(0)  评论(0)    收藏  举报