系统设计:实时聊天系统

核心需求

  • 会话列表、历史消息和实时收发。
  • 消息发送中、成功、失败、已读等状态。
  • 断线重连、多端同步和消息去重。
  • 图片、文件、长列表与未读数。

数据通道

  • HTTP:登录、会话列表、历史消息、文件上传。
  • WebSocket:实时消息、状态变化和已读回执。
  • CDN/对象存储:图片和文件内容。

不要把大文件直接塞进 WebSocket。先走分片上传,实时消息只传文件元数据和地址。

消息模型

type Message = {
  id: string;
  clientId: string;
  conversationId: string;
  senderId: string;
  sequence: number;
  sentAt: number;
  status: 'sending' | 'sent' | 'failed';
  content: unknown;
};
  • clientId:客户端发送前生成,用于乐观展示和请求去重。
  • id:服务端最终消息 ID。
  • sequence:用于排序、发现缺口和增量同步。

时间戳不能单独保证严格顺序,因为客户端时钟可能不准且多条消息时间可能相同。

发送流程

  1. 客户端生成 clientId,立即显示 sending 消息。
  2. 通过连接发送消息,服务端按 clientId 保证幂等。
  3. 收到确认后替换服务端 ID 和状态。
  4. 超时标记失败,允许用户手动重试。

如果连接在确认前断开,重发相同 clientId,服务端应返回已有结果而不是创建重复消息。

断线恢复

  • 心跳检测半开连接。
  • 指数退避重连并加入随机抖动。
  • 携带每个会话最后序号请求增量消息。
  • 增量范围过大或序号失效时重新获取快照。
  • 浏览器多标签页可选出一个连接拥有者,减少重复长连接。

前端性能

  • 历史消息使用游标分页和虚拟列表。
  • 图片使用缩略图、懒加载和尺寸占位。
  • 会话状态按会话 ID 归一化,避免复制大数组。
  • 限制内存缓存的会话和消息数量。
  • 输入状态、消息列表和未读数分开订阅。

安全与可观测性

  • 服务端校验用户是否属于会话。
  • 文件检查类型、大小和访问权限。
  • 富文本必须进行严格净化。
  • 监控连接成功率、重连次数、消息确认延迟和序号缺口。