题目重点:分离持久化消息、最近消息缓存、在线推送与未读游标,处理重连、热点群和重复发送。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练采用每群内部单调序号,消息在持久化成功后才ACK;是否需要跨群顺序、撤回/编辑需另确认。
- 最近窗口取40条,在线WebSocket推送,离线保留未读游标;Redis是可重建缓存。
- 容量示例假设消息正文平均1KB、每群每秒10条、80%成员在线;这些数值用于计算,不是实际业务流量。
解题路线
- API拆send(group_id,client_msg_id,body)、history(group_id,before_seq,limit)、read_cursor(group_id,user_id,last_read_seq)。服务端核成员资格,client_msg_id在用户/群作用域幂等,不以请求到达次数发多条。
- 持久化Messages(group_id,seq,message_id,sender,body)与可重放发布事件。事务或outbox保证已ACK消息最终能通知;不在数据库写失败时先向500人发送“成功”消息。
- Redis按群缓存最新40条及版本水位,读取缺口回源数据库补齐。缓存顺序按seq,允许重复消息按ID去重;并发填充旧结果不能覆盖新水位,采用版本比较或同分区串行更新。
- 在线gateway维护用户连接与群订阅,按群事件扇出给在线成员;送达ACK和用户已读不同。离线推送可以合并成“群有新消息”,不为每条消息立即发500次手机通知。
- 重连客户端携带last_received_seq拉取缺口,推送与补拉可能重叠,按消息ID/seq去重。Redis失效时不丢持久化历史;旧连接未退出仍订阅时需要连接epoch或状态清理。
- 500人并不自动使每群读写都成为瓶颈。假设10msg/s与400在线成员,扇出约4000条/s、正文约4MB/s,另加协议开销;先限制每连接缓冲与慢消费者,再考虑群分区和gateway扩容。
- 最近40条正文约40KB/群;百万活跃群约40GB仅正文,不能忽略对象、索引、副本和Redis淘汰。LRU是淘汰策略,Redis是缓存服务,二者不是互斥选项。验收关注消息不丢、延迟分位、重连补齐与热点慢用户隔离。
手动走一遍
消息seq101已落库但gateway发布前崩溃,outbox重放使101最终通知;客户端在重连补拉100..103后又收到101推送,只渲染一次。最近缓存只有120..159,客户端要求110之前的历史必须回源,不能把缓存缺失解释为无消息。
规模与取舍
发送持久化按分区索引/事务成本计,在线扇出O(在线成员数),历史查询约O(log M+limit)。最近缓存O(活跃群数×40×平均消息大小),未读游标O(群成员关系数);不能每用户缓存完整群历史而不计算放大。
易错点与追问
容易失分的边界
- Redis成功即认为消息可靠。
- 每成员一份完整消息历史。
- 未读和送达混成一个状态。
- 重连补拉与推送重复渲染。
- 慢连接队列无限增长。
继续深挖
- 撤回消息怎样同步到离线设备?
- 退出群之后还能否查旧历史?
- 同群每秒十万消息会卡在哪里?
- 多设备已读游标怎样合并?
技术参考
- RFC6455 WebSocket协议:传输机制参考;应用消息持久化、顺序、重连补齐由本文设计定义。
