第13章IM服务——13.8 阶段性汇总:存储设计

13.8 阶段性汇总:存储设计

在介绍完IM服务的主要逻辑后,我们对消息数据的存储进行正式设计。

存储消息本身的消息表:修改消息数据的场景极少见,更多的场景是使用消息ID获取消息数据,所以使用分布式KV存储系统或传统数据库都是合适的选型。如果使用分布式KV存储系统,则以消息ID为Key,以消息内容、发送时间、发送者、消息状态等信息组合的JSON格式为Value;如果使用传统数据库,则消息表的结构如表13-1所示。

image-20250503225239121

消息表使用msg_id字段作为唯一索引,以提高根据消息ID获取消息数据的效率。

管理会话数据的会话表:其主要用途是根据会话ID获取会话信息。会话表的结构如表13-2所示。

image-20250503225320570

会话表使用conversation_id字段作为唯一索引,且使用VARCHAR类型来表75,这是为了满足13.6.1节在创建单聊会话时使用字符串类型的会话ID的需要。

会话消息链:用于支持读扩散模式的消息存储,其重点是保证会话内消息有序。其数据表conversation_msg_list的结构如表13-3所示。

image-20250503225406826

会话消息链主要用于按照会话查询有序的消息ID列表,所以需要为conversation_msg_list数据表的conversation_id和seq字段创建联合索引sort_msg_idx(conversation_id, seq),这样一方面可以保证通过会话ID获取到消息列表,另一方面可以保证消息列表按照seq字段有序排列。

当用户根据会话X的已读消息read_seq获取N条消息时,执行如下SQL语句:

1
SELECT * FROM conversation_msg_list WHERE conversation_id = X AND seq > read_seq LIMIT N

当用户获取会话X的历史消息时,根据当前消息的Seq current_seq向前查找N条历史消息,执行如下SQL语句:

1
SELECT * FROM conversation_msg_list WHERE conversation_id = X AND seq < current_seq LIMIT N

当用户补偿拉取会话X中指定消息Seq区间[seq_i, seq_j的消息时,执行如下SQL语句:

1
SELECT * FROM conversation_msg_list WHERE conversation_id = X AND seq >= seq_i AND seq <= seq_j

用户消息链:与会话消息链相同,服务于写扩散模式的用户消息链也应该有一个类似结构的数据表user_msg_list,如表13-4所示。

image-20250503225644587

用户消息链的使用场景与会话消息链的类似,区别是它服务于写扩散模式,应该查询的是用户维度下有序的消息列表,所以需要为user_msg_list数据表的user_id和seq字段创建联合索引sort_msg_idx(user_id, seq)。其根据消息的Seq查询消息列表的SQL语句与会话消息链的类似。

记录用户与会话关联关系的用户会话链:其承载的职责(至少)如下。

  • 根据会话ID可以获取会话参与用户的列表。
  • 根据用户ID可以获取与其相关的会话列表,尤其是最近有新消息的N个会话。
  • 根据用户ID和会话ID获取某用户是否是某会话的相关用户。
  • 记录用户ID在某会话中最后一条已读消息的Seq,便于实现消息回执功能。
  • 记录用户ID在某会话中的设置,比如消息是否强提醒或被屏蔽、会话是否置顶等。

其数据表user_conversation_list的结构如表13-5所示。

image-20250503225803070

在推送消息、查看会话详情时都需要获取与会话ID相关的参与用户列表,所以我们需要为user_conversation_list数据表的conversation_id和user_id字段创建联合索引 conversation_user_idx(conversation_id, user_id)。根据会话ID X查询用户列表的SQL语句如下:

1
SELECT user_id FROM user_conversation_list WHERE conversation_id = X

即时通信还有很多根据用户获取会话的场景,例如:

  • 查询用户是否属于某会话;
  • 查询用户在某会话中的设置,如是否置顶、消息通知类型等;
  • 查询最近与用户相关的最多N个会话列表,比如微信,用户在切换新设备时要登录账号。

我们可以为用户会话链的user_conversation_listt数据表的user_id和conversation_id字段创建联合索引user_conversation_idx(user_id, conversation id),在查询用户A是否属于会话X,或者查询用户A在会话X中的设置时,执行如下SQL语句:

1
SELECT * FROM user_conversation_list WHERE user_id = A AND conversation_id = X

如果用户切换了登录设备,则可能涉及获取最近的活跃会话列表,我们可以在根据user_id字段查询会话列表后,选取last_read_seq字段值最大的N条记录作为最近N个活跃会话。