← 返回面试专题

💬 IM 聊天系统如何设计?

核心目标:实时消息推送、离线消息同步、消息有序、多端同步

🧩 典型题干

设计一个聊天系统: - 支持实时消息推送 - 离线消息同步 - 消息有序 - 多端同步

📚 学习正文:IM 系统如何做到实时又可靠?

交互图:IM 关键链路闭环(点步骤看可靠性与一致性落点)

面试表达建议:连接是入口(长连接网关+路由表) → 消息先落库(msgId/seq) → 推送是优化体验(在线投递) → 拉取保证一致(离线增量) → ACK/重试保证不丢

1) 建联与路由:长连接不要挂在业务服务上

  • 连接网关:WebSocket/TCP 长连接集中由网关维护,业务服务无状态可水平扩展。
  • 路由表:维护 userId -> gatewayNode/connId(Redis/注册中心/本地缓存+订阅)。
  • 保活与迁移:心跳超时清理路由;断线重连会更新路由。

1) 需求澄清与量级

2) 核心挑战

实时推送:长连接管理、心跳保活、断线重连 消息有序:单会话内消息严格递增 离线同步:用户上线后拉取离线消息 多端同步:手机/PC/Web 端消息一致

3) 数据模型设计

4) 消息投递架构

1) 发送者发消息到 API 网关 2) 消息服务持久化到 DB 3) 推送服务实时推送给在线用户 4) 离线用户消息存储到离线表 5) 用户上线时同步离线消息

系统架构图(面试时用它讲全链路)

客户端(App/Web/PC) | v 连接网关(WebSocket/TCP)集群 - 心跳保活 / 断线重连 - 连接鉴权 - 维护 userId -> connId | +--> 路由表(Redis/注册中心): userId -> gatewayNode | v 消息接入服务(API) - 鉴权/限流/幂等(clientMsgId) - 生成 msgId / 会话 seq | +--> 消息存储(DB/分区表/冷热分层) | +--> MQ(可选):投递事件/回执事件/离线补偿 | v 投递/推送服务 - 查路由 -> 投递到目标网关 - 在线投递、多端同步 - 群聊扇出(写扩散/读扩散) | v 客户端 ACK/回执 - 投递 ACK(至少一次 + 去重) - 已读回执同步(多端) 离线与一致性:客户端按 lastSeq 拉取增量兜底;超时重试/对账补偿修复异常

5) 实时推送实现

6) 可靠性保障

消息去重:基于消息ID的客户端去重 离线同步:可靠的离线消息存储和拉取 多端同步:基于序列号的消息同步机制 监控告警:消息延迟、推送失败率监控

🎯 面试题(建议学完上面正文再做)

1. 为什么需要“连接网关”和“路由”?中等
  • 长连接需要保持在特定节点上,业务服务不适合直接承载海量连接。
  • 需要“用户 -> 网关节点”的映射(路由表),才能把消息推到正确连接。
  • 路由表可存在 Redis/注册中心,变更靠心跳/下线清理。
2. 消息可靠投递怎么做?(至少一次/不丢)困难
  • 先落库再投递:服务端生成 msgId/seq,写入消息表后再推送。
  • ACK 机制:客户端 ack 后标记已读/已送达;超时重发。
  • 幂等:客户端按 msgId 去重;服务端重试不会重复展示。
⚠️ 追问点:“不丢”和“不会重复展示”是两件事,要分别用重试与去重解决。
3. 离线消息与多端同步怎么做?困难
  • 会话 seq:每个会话递增序号,客户端用 lastSeq 拉取增量。
  • 离线拉取:登录后按 lastSeq 拉取缺失消息。
  • 多端一致:服务端维护每端游标或统一游标;已读回执也要同步。
💡 面试表达:推送是优化体验,真正保证一致性的核心是“拉取增量(基于 seq)”。
4. 群聊怎么扩展?(扇出)中等
  • 写扩散:发消息时把消息写到每个成员收件箱(读快写重)。
  • 读扩散:只写群消息一次,读时按成员拉取(写轻读重)。
  • 混合:小群写扩散,大群读扩散或分层推送。
5. 面试 2 分钟总结怎么讲?简单
  1. 连接网关承载长连接,路由表记录用户在哪台网关。
  2. 消息服务先落库生成 msgId/seq,再投递,ACK 超时重试保证不丢。
  3. 客户端以 seq 拉取增量保证最终一致,多端同步靠游标与回执。
  4. 群聊按规模选择写扩散/读扩散;全链路有监控与限流。