面试知识库
困难

IM系统设计#

一句话答案#

IM 系统核心架构:客户端通过长连接(WebSocket/TCP)接入网关层,消息经 MQ 异步投递到接收方网关再推送,离线消息存 DB/Redis 拉取;通过消息 ID 去重 + ACK 确认 + 序号排序保证可靠有序。

核心要点

整体架构#

┌──────────┐    WebSocket    ┌──────────────┐     MQ      ┌──────────────┐    WebSocket    ┌──────────┐
│ 发送方 A  │ ──────────────→│ 接入网关层    │ ──────────→ │ 接入网关层    │──────────────→ │ 接收方 B  │
└──────────┘                 └──────┬───────┘             └──────────────┘                 └──────────┘

                              ┌─────┴─────┐
                              │  消息服务   │
                              │ (存储+路由) │
                              └─────┬─────┘

                    ┌───────────────┼───────────────┐
                    │               │               │
              ┌─────┴─────┐  ┌─────┴─────┐  ┌─────┴─────┐
              │  消息 DB   │  │  Redis    │  │  MQ       │
              │(历史消息)   │  │(在线状态)  │  │(异步投递)  │
              └───────────┘  └───────────┘  └───────────┘
plaintext

核心设计点#

1. 连接管理

  • 长连接协议:WebSocket(Web)/ 自定义 TCP(App)
  • 心跳保活:客户端 30s 发一次心跳,服务端 90s 未收到断开
  • 连接网关:维护 userId → channelId 映射,存 Redis(分布式场景)

2. 消息投递模型

在线投递(推模式):
  A → 网关 → 消息服务(存DB + 写MQ)→ 路由查 B 在哪个网关 → 推送给 B

离线投递(拉模式):
  B 上线 → 拉取离线消息(按 lastSeq 增量拉取)
plaintext

3. 消息可靠性

机制作用
消息 ID(全局唯一)去重
序列号(递增 seq)排序 + 断点续拉
客户端 ACK确认收到,未 ACK 则重推
服务端持久化消息入库后才返回发送成功

4. 消息存储

单聊:写扩散(每个人一份收件箱)
  - 优点:读简单,按 userId 分库
  - 缺点:写放大

群聊:读扩散(群消息存一份,成员各自记录已读位点)
  - 优点:写不放大
  - 缺点:读时需合并
  - 大群(>500人)一般用读扩散 + 推送离线通知
plaintext

5. 群消息优化

  • 小群(<200人):写扩散到每人收件箱
  • 大群(>200人):读扩散 + 消息存群维度 + 维护每人 lastReadSeq
  • 万人群:不推全量消息,只推通知(有新消息),客户端按需拉取

消息序号设计#

方案1:Redis INCR per conversation(简单,但 Redis 单点)
方案2:Snowflake 全局 ID(天然有序,但跨 conversation 不连续)
方案3:号段分配(发号器批量分配 seq 段,本地递增)

推荐:conversation 粒度的 Redis INCR + 本地缓存号段
plaintext

已读回执 & 消息撤回#

已读回执:B 读到 seq=100 → 发 ACK(conversation, seq=100) → A 端更新"已读"
消息撤回:2分钟内可撤回 → 发撤回消息 → 服务端标记 + 推送撤回通知给对方
plaintext
面试回答(2分钟版)

IM 系统核心分三层:接入层用 WebSocket 维护长连接,业务层处理消息路由和存储,存储层用 MySQL 存历史消息、Redis 存在线状态和未读计数。发消息流程:A 发消息到网关,消息服务先持久化再通过 MQ 异步路由到 B 所在网关推送。B 不在线则存离线消息,上线后按 lastSeq 增量拉取。可靠性靠三个机制保证:全局唯一消息 ID 去重、递增 seq 排序和断点续拉、客户端 ACK 确认未收到则重推。群消息的关键设计是写扩散和读扩散的选择:小群写扩散到每人收件箱方便读取,大群读扩散只存一份减少写放大。消息序号用 conversation 粒度的 Redis INCR。连接保活用 30s 心跳,超时断开触发离线流程。

追问与易错

追问方向:

  • “怎么保证消息不丢?”→ 先持久化再 ACK 给发送方 + 接收方 ACK 确认
  • “百万长连接怎么维护?”→ 多网关分摊 + epoll 单机 10w+ 连接 + 心跳检测
  • “消息乱序怎么办?”→ 客户端按 seq 排序展示,发现 seq 断裂主动拉取
  • “群消息已读怎么做?”→ 每人维护 lastReadSeq,对比群 maxSeq 算未读数

易错点:

  • ❌ “所有消息都推模式”——离线消息必须拉模式,否则 MQ 积压
  • ❌ “群聊用写扩散”——万人群写扩散会爆炸,大群必须读扩散
  • ❌ “WebSocket 断了消息就丢了”——有离线存储 + 上线拉取兜底