系统设计:实时聊天系统
核心需求
- 会话列表、历史消息和实时收发。
- 消息发送中、成功、失败、已读等状态。
- 断线重连、多端同步和消息去重。
- 图片、文件、长列表与未读数。
数据通道
- 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:用于排序、发现缺口和增量同步。
时间戳不能单独保证严格顺序,因为客户端时钟可能不准且多条消息时间可能相同。
发送流程
- 客户端生成
clientId,立即显示 sending 消息。
- 通过连接发送消息,服务端按
clientId 保证幂等。
- 收到确认后替换服务端 ID 和状态。
- 超时标记失败,允许用户手动重试。
如果连接在确认前断开,重发相同 clientId,服务端应返回已有结果而不是创建重复消息。
断线恢复
- 心跳检测半开连接。
- 指数退避重连并加入随机抖动。
- 携带每个会话最后序号请求增量消息。
- 增量范围过大或序号失效时重新获取快照。
- 浏览器多标签页可选出一个连接拥有者,减少重复长连接。
前端性能
- 历史消息使用游标分页和虚拟列表。
- 图片使用缩略图、懒加载和尺寸占位。
- 会话状态按会话 ID 归一化,避免复制大数组。
- 限制内存缓存的会话和消息数量。
- 输入状态、消息列表和未读数分开订阅。
安全与可观测性
- 服务端校验用户是否属于会话。
- 文件检查类型、大小和访问权限。
- 富文本必须进行严格净化。
- 监控连接成功率、重连次数、消息确认延迟和序号缺口。