SWE · VO System Design

Uber VO Messenger 设计:服务端单勾、已读双勾与 Redis 边界

合并 Messenger 和 WhatsApp 类面经,按题目定义服务端接受为单勾、已读为双勾;设计消息持久化、去重、会话顺序、多设备同步及 Redis 的实际职责。

题目重点:合并 Messenger 和 WhatsApp 类面经,按题目定义服务端接受为单勾、已读为双勾;设计消息持久化、去重、会话顺序、多设备同步及 Redis 的实际职责。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练范围先做一对一文本消息,保留离线收件、历史同步和已读状态;群聊、端到端加密、撤回及附件作为追问。两篇题目未披露用户规模和延迟目标。
  • 一个勾仅代表服务端已经持久接受消息,不代表送达接收方设备;两个勾代表接收方发出可信的已读确认。没有题目证据要求另设‘送达设备’中间勾。
  • 示例容量以 100 万在线连接、峰值 10 万条消息/秒推演,纯属训练数字;应根据实际服务水平目标重新估算。

解题路线

  1. 发送 API 带 conversationId、senderId、clientMessageId、正文和认证身份;服务端在同一会话内分配单调序号 seq,把 (conversationId,senderId,clientMessageId) 设唯一键。消息及 outbox 事件在一次持久事务内提交,随后才给发送方服务端 ACK/单勾;超时重试同键返回同一 serverMessageId。
  2. 分发服务从 outbox/日志向接收方在线连接推送,失败时保留待投递记录;客户端按 seq 补齐断线间隙,重复推送按 serverMessageId 去重。会话顺序可按 conversationId 分区单写或协调序号,不能仅依赖多个网关本地时钟。
  3. 接收方在真正展示/阅读消息后提交 readUpToSeq;服务端记录每个会话/接收方的最大已读序号,使用 max(old,new) 保持单调,并把回执异步通知发送方。多设备中哪一台的阅读算已读要先定义,训练口径为任一授权设备确认即算。
  4. Redis 可用于在线连接路由、最近会话缓存、短期去重或未读计数;消息事实、ACK 状态和已读游标必须有持久可恢复存储或明确 Redis 持久化/复制风险。说‘Redis 高吞吐’不能代替写入持久性、故障恢复和容量推演。
  5. 系统按消息 ID、会话 seq 和回执游标做幂等;监控提交到 ACK 延迟、分发延迟、断线补拉、重复推送、已读回执积压及数据库与缓存差异。消息保留和已读可见性要遵守产品及隐私设置。

手动走一遍

发送者以 clientMessageId=M7 发送,服务端提交 messageId=S9、seq=42 后回单勾。网络超时让 M7 重试,唯一键仍返回 S9,不新增一条消息。接收者离线时保持单勾;上线补拉 S9 并打开会话后回 readUpToSeq=42,发送者显示双勾。若先收到 seq=43 回执再收到延迟的 42,游标仍停在 43。

规模与取舍

消息量 M 是主要持久存储,写入与事件分发理想情况下各 O(1) 均摊;每个会话/用户组合存一个已读游标,空间随活跃参与关系增长。峰值消息 QPS 决定分区数、日志吞吐和下游 fanout;群聊若 N 人收件,投递工作量可能随 N 线性增长。

易错点与追问

容易失分的边界

  • 把题目的单勾误释为‘接收方设备已送达’;题目明确是传到服务器。
  • 服务端只写 Redis 缓存就发单勾,却未说明持久化与故障时消息是否会丢。
  • 重试生成新 messageId,导致重复消息与错乱 seq;已读回执不按最大序号合并会倒退。
  • 多实例各自分配局部 seq,发送方与接收方看到不同顺序;需要会话范围的顺序保证。

继续深挖

  • 群聊中双勾是任一成员读、所有成员读还是显示每人已读列表?需改变回执存储和 UI 契约。
  • 端到端加密后,服务端如何路由、去重和保存密文而不读明文?密钥换设备怎么处理?
  • 若某用户禁用已读回执,双勾状态如何与对方隐私设置协调?
✓已复制微信号!