Yihui’s Blog

即时通讯历史消息分页

日期:2026-07-11
标签:#面试 #八股 #后端 #系统设计 #场景题

一句话答案

历史消息应使用基于 conversationId + seq/messageId 的游标分页,按游标向更早消息查询,避免 Offset 在新消息插入后产生重复、遗漏和深分页性能问题。

面试口语版

每个会话维护单调递增 seq,消息表按会话和 seq 建联合索引。首次进入查询 conversation_id = ? ORDER BY seq DESC LIMIT n;继续下拉时携带当前最小 seq,查询 seq < cursor ORDER BY seq DESC LIMIT n。服务端返回下一游标和 hasMore,客户端按 messageId 去重,结果展示时再转为正序。群聊可按会话分片,冷热分层,撤回或删除用状态字段或操作事件处理。

SQL 示例

SELECT *
FROM messages
WHERE conversation_id = ? AND seq < ?
ORDER BY seq DESC
LIMIT 30;

关键细节

  • 游标必须是稳定排序键;时间戳可能重复,应使用 (send_time, message_id) 复合游标或会话 seq。
  • 多端同步要区分“历史分页游标”和“已读游标”。
  • 新消息到达不影响向旧消息方向翻页。
  • 大群热点可按会话分区并使用缓存,旧消息归档到低成本存储。

面试官追问

  1. seq 如何生成?
  2. 消息撤回后分页如何处理?
  3. 如何保证多端消息顺序?
  4. 跨库后如何查询会话历史?

面试官追问参考答案

1. seq 如何生成?

只需保证会话内单调递增,不必全局有序。可以由会话路由到固定分片后使用数据库/Redis 原子自增,或由单分区日志 Offset 作为 seq;高吞吐时按会话或分片预取号段,但要接受跳号。

2. 消息撤回后分页如何处理?

不要物理删除导致客户端状态不一致,而是记录撤回状态或追加一条撤回操作事件。历史查询返回占位消息和最新状态;客户端收到撤回事件按 messageId 更新已展示消息,权限和撤回时限由服务端校验。

3. 如何保证多端消息顺序?

服务端以会话 seq 作为权威顺序,所有端按 seq 去重、排序并检测缺口,发现缺口就拉取补齐。网络到达顺序不可靠;本地临时消息使用 clientMsgId 关联服务端确认后的正式 seq。

4. 跨库后如何查询会话历史?

按 conversationId 稳定路由到单一分片,使同一会话消息尽量不跨库,游标中包含分片信息和 seq。若历史因迁移或冷热分层跨多个存储,查询层根据路由元数据并行读取,再按 seq 归并,迁移期间需兼容双读和去重。

维护与整理 · Yihui在 GitHub 上编辑

继续阅读

浏览全部文章