10万并发IM系统架构设计方案
文档信息
| 项目 | 内容 |
|---|---|
| 文档标题 | 10万并发IM系统架构设计方案 |
| 技术负责人 | 架构师团队 |
| 创建日期 | 2026-01-17 |
| 更新日期 | 2026-01-17 |
| 版本号 | V1.0 |
| 文档状态 | 正式版 |
一、业务需求概述
1.1 核心业务场景
构建一个支持10万并发在线用户的高性能IM即时通讯系统,满足以下核心场景:
业务场景说明:
- 用户规模:平台支持10万用户同时在线
- 消息类型:支持单聊、群聊、系统通知、实时状态同步
- 典型分布:平均每个用户同时加入5-10个会话,活跃用户占比30%
核心功能:
- 单对单聊天:支持文本、图片、语音、视频消息的实时收发
- 群组聊天:支持群聊、讨论组,单群最高5000人
- 在线状态:实时同步用户在线/离线/忙碌等状态
- 消息推送:支持离线消息推送、未读消息提醒
- 消息漫游:支持消息历史记录的多端同步
- 断线重连:支持网络闪断后的自动重连和消息补发
- 消息可靠性:保证消息不丢失、不重复、有序送达
1.2 性能指标要求
| 指标项 | 目标值 | 说明 |
|---|---|---|
| 并发在线用户数 | 10万+ | 支持10万用户同时保持长连接 |
| 消息延迟 | < 200ms | 点对点消息端到端延迟 |
| 群消息延迟 | < 500ms | 1000人群消息全员到达延迟 |
| 连接建立时间 | < 1秒 | 用户登录到建立长连接的时间 |
| 系统可用性 | 99.99% | 年停机时间不超过52.56分钟 |
| 消息送达率 | 99.99% | 消息成功送达率 |
| 断线重连时间 | < 3秒 | 网络恢复后自动重连时间 |
| 消息吞吐量 | 10万QPS | 系统总消息处理能力 |
1.3 技术挑战
- 海量长连接:10万并发长连接的维持,每个连接占用内存和CPU资源
- 消息可靠性:网络不稳定情况下保证消息不丢失、不重复、有序
- 低延迟:毫秒级消息投递延迟要求
- 高可用:服务宕机不影响整体服务,快速故障转移
- 断线重连:网络闪断后快速重连,消息补发不遗漏
- 水平扩展:支持动态扩容,应对突发流量
- 状态同步:用户状态、会话状态的实时同步
- 消息存储:海量历史消息的存储和快速检索
二、架构核心理念
2.1 为什么选择长连接?
IM系统的本质需求:实时双向通信
短连接轮询 vs 长连接对比:
短连接轮询(HTTP): 长连接(WebSocket/TCP):
┌────────────────────┐ ┌────────────────────┐
│ 客户端 │ │ 客户端 │
└─────┬──────────────┘ └─────┬──────────────┘│ 1秒一次轮询 │ 一次连接│ GET /messages │ WebSocket握手↓ ↓
┌────────────────────┐ ┌────────────────────┐
│ 服务器 │ │ 服务器 │
│ 返回:无新消息 │ │ 保持连接 │
└────────────────────┘ │ 有消息主动推送 │└────────────────────┘
每秒10万次请求 10万个长连接
带宽:10万 × 1KB = 100MB/s 带宽:心跳包 ≈ 10KB/s
服务器压力:极大 服务器压力:可控
实时性:差(1秒延迟) 实时性:好(毫秒级)
结论:IM系统必须使用长连接
2.2 架构设计原则
- 接入层无状态化:接入服务器不保存用户状态,支持水平扩展
- 消息队列解耦:使用MQ解耦消息生产和消费,提高可靠性
- 状态集中管理:用户在线状态、连接映射关系集中存储在Redis
- 分层架构:接入层、逻辑层、存储层分离,职责清晰
- 读写分离:消息写入和消息读取分离,优化性能
- 多级缓存:热点数据多级缓存,减少数据库压力
- 异地多活:支持多机房部署,就近接入
三、整体架构设计
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. 推送离线消息 │ ││ <───────────────── │ ││ │ │
关键点:
- 认证在建立连接后:先握手,后认证,避免无效连接占用资源
- 上报在线状态:Redis记录
userId → serverId映射 - 离线消息推送:连接成功后立即推送离线消息
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人 ││ │ │ │ │
群消息优化:
- 扇出写:一条消息写入,多次读取(每个群成员一次)
- 异步推送:避免阻塞发送方
- 限流保护:大群(1000人+)消息限流,防止雪崩
- 离线合并:离线用户消息合并推送,减少推送次数
大群优化策略:
小群(<100人) :实时推送每个成员
中群(100-1000人):实时推送在线成员,离线成员合并推送
大群(>1000人) :批量推送,分批次,延迟容忍度更高
4.3 断线重连机制
4.3.1 断线检测
断线场景:
- 客户端主动断开:用户退出、杀进程、切换网络
- 网络闪断:电梯、地铁、WIFI切换4G
- 服务器主动断开:心跳超时、服务升级
- 中间设备断开: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. 推送断线期间消息 │ ││ <───────────────── │ ││ │ │
关键点:
- 携带lastMsgId:客户端记录最后收到的消息ID,重连后从该位置补发
- 去重处理:服务器根据msgId去重,避免重复推送
- 消息缓冲: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个连接 │└───────┬────────┘│┌───────▼────────┐│ 客户端检测到断线││ 自动重连到其他 ││ 健康节点 │└────────────────┘
容灾措施:
- 健康检查:LVS每3秒检查一次,连续3次失败摘除节点
- 冗余部署:实际部署65台(10万÷2000×1.3冗余)
- 故障转移时间:检测(9秒)+ 重连(3秒)= 12秒内恢复
- 故障影响面:单台宕机影响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 消息去重
问题:网络重传、重连补发导致消息重复
去重策略:
- 客户端去重:根据msgId去重
// 客户端维护已收消息集合(LRU缓存,最近1000条)
Set<String> receivedMsgIds = new LRUCache<>(1000);function onReceiveMessage(message) {if (receivedMsgIds.contains(message.msgId)) {// 重复消息,忽略return;}receivedMsgIds.add(message.msgId);// 处理消息displayMessage(message);
}
- 服务端去重:Redis记录已送达消息
# 记录已送达消息(7天过期)
msg:delivered:{userId}:{msgId} → timestamp
TTL: 7天
5.2.3 消息有序性
问题:网络乱序、并发推送导致消息顺序错乱
有序性保证:
- 单聊消息有序:
Kafka分区策略:hash(senderId + receiverId) % partition_num
同一对用户的消息落到同一分区,保证有序
- 群聊消息有序:
Kafka分区策略:hash(groupId) % partition_num
同一个群的消息落到同一分区,保证有序
- 客户端排序:
// 客户端根据消息序列号(seqId)排序
List<Message> messages = ...;
messages.sort((m1, m2) -> m1.seqId - m2.seqId);
5.3 限流与降级
5.3.1 限流策略
限流目的:防止突发流量打垮系统
限流维度:
- 用户级限流:
单用户发消息频率:10条/秒(令牌桶算法)
单用户群发消息:5条/秒Redis计数器:
rate:limit:user:{userId} → count
TTL: 1秒
- 群组级限流:
小群(<100人) :100条/秒
中群(100-1000):50条/秒
大群(>1000) :20条/秒
- 全局限流:
接入服务器:每台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, // 会话IDtimestamp: -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防护
措施:
- 接入层限流:单IP连接数限制(10个/IP)
- 云服务DDoS防护:阿里云DDoS高防
- 黑名单:自动封禁异常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系统,核心特点:
- 高并发:单机2000连接,65台服务器支撑10万并发
- 高可用:多层冗余,故障自动转移,可用性99.99%
- 低延迟:消息端到端延迟<200ms,群消息<500ms
- 高可靠:三重ACK保证消息不丢失,去重保证不重复
- 断线重连:指数退避重连,3秒内恢复,消息自动补发
- 可扩展:无状态设计,支持水平扩展,弹性伸缩
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 未来优化方向
- 智能路由:AI预测用户活跃时段,提前预热连接
- 边缘计算:CDN边缘节点部署IM服务,进一步降低延迟
- 音视频通话:集成WebRTC,支持实时音视频
- 消息检索:ElasticSearch全文检索,快速查找历史消息
- 智能推荐:基于用户行为的好友推荐、群组推荐
- 消息翻译:多语言实时翻译,支持跨语言沟通
附录
附录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)故障转移:客户端自动重连到健康节点。
文档结束
如有疑问,请联系架构师团队。