通过前面对 IM 分层架构系统的学习,我们已经了解了入口网关层 Entry、路由层 Router 和数据访问层 Das 的核心职责和逻辑设计,今天我们对 IM 系统最关键的数据结构部分进行分析,即 Das 的领域模型设计。
领域模型是对现实世界中对象的可视化表示,IM 系统最核心的模型为 “离线消息”、“联系人” 和 “云消息”,针对这三个核心模型,我们分析其数据库表的结构设计;开发经验丰富的同学,可以通过数据表结构的设计思路推测出上层业务逻辑流程。
一、离线消息
当前用户处于离线状态,其他用户对其发送消息时,需要在 “离线消息” 表中存储消息记录;当前用户登录后,直接访问 “离线消息” 表,从该表中拉取当前用户所有的离线消息。“离线消息表” 设计见下图。
举例:用户 uid=101 发消息 “你好!” 给用户 uid=102,102处于离线状态,此时需要在 “离线消息表” 中保存记录如下:
msg_id 是主键字段,根据时间趋势递增; del_flag 字段用来表示消息是否拉取,默认为 0,拉取之后将字段值更新为 1;然后由离线任务定时物理删除 30 天之前的消息记录。 在 基于 B+ 树的 MySQL InnoDB 存储引擎中,这样设计可以防止存储空洞,极大提升数据库的操作效率。
另外,当离线消息数量较大时,通过 uid 作为分表索引字段对离线消息表进行分表,保证同一用户的所有离线消息能落在一张表中。
二、联系人
联系人,即好友,有过消息收发记录的两个用户即是联系人关系。联系人模型稍微复杂一些,需要重点考虑两个问题:
- 联系人定制化需求。举例:张三发消息给李四后,李四是张三的联系人,张三也是李四的联系人;当张三把联系人李四删除后,在李四的联系人列表中,仍然会有张三,即李四不是张三的联系人了,但是张三仍然是李四的联系人。对联系人的操作,除了 “删除”,还有 “收藏”、“点赞”等,含义一致;简单总结就是:张三是李四的联系人,李四是张三的联系人,这是两个物理对象,相互之间需要隔离。
- 联系人分库存储需求。当联系人记录数量很大时,通常需要分库分表进行存储,我们需要将同一个用户的所有联系人记录落在同一张表中,避免在用户获取联系人记录时对所有的数据表进行遍历。
为解决上述两个问题,当发一条消息成为联系人时,在联系人表中分别站在消息收发双方的维度保存两条记录;“联系人表” 设计见下图。
举例:用户 uid=101 发消息 “ 你好!” 给用户 uid=102 ,此时需要在 “联系人表” 中保存两条记录,见下图。
第一条记录,to_uid=102 是消息的接收方,from_uid=101是消息的发送方,direction=1表示消息方向是正的,即正向思路;而在第二条记录中, to_uid=101,from_uid=102,由 direction=0表示消息方向是反向的,即反向思路;也就是,我们可以根据字段 direction 判断出当前记录中,谁是消息的发送方,谁是消息的接收方。
在这两条记录中,消息内容字段 content 和 消息的更新时间字段 timestamp 是一致的,对于联系人来说,每发一条消息,需要对记录做 “更新” 操作,而非 “插入”,所以 timestamp 存储 “更新时间”。
del_flag 是删除联系人的标记字段,在上述例子中,如果用户 102 删除了联系人 101,则需要将 del_flag 字段更新为 1。
在该例子中,用户 uid=101 发消息给用户 uid=102,所以 to_uid=102 的记录中,未读数 unread=1,而 to_uid=101 的记录中,其未读数 unread=0。
从另一个视角理解 “联系人表” 的设计,to_uid 是主属性,其他所有字段都是围绕 to_uid 进行描述的。当联系人记录数量非常高时,通过 to_uid 作为分表索引字段对联系人表进行分表,保证同一用户的所有联系人记录能落在同一张表中。
三、云消息
云消息,即所有历史记录消息;云消息表设计思路与联系人表类似,需要解决相同的两个问题:
- 消息定制化需求。张三发消息给李四,张三删除这一消息后,李四仍然可以看到这条消息。
- 消息分库存储需求。消息分库之后,保证同一用户的所有消息能落到同一张表中。
解决这两个问题的思路与联系人表类似,即用户每发一条消息,在云消息表中分别站在消息的收发双方维度分别存储一条记录;简言之:发一条消息,存两条消息记录。这就是消息的 “写扩散模型” 实现,在后续文章中会结合群消息业务进行深入分析。“云消息表” 设计见下图。
举例:用户 uid=101 发消息 “ 你好!” 给用户 uid=102 ,此时需要在 “云消息表” 中保存两条记录,见下图。
字段 to_uid、from_uid 和 direction 设计思路同 “联系人表”,不再赘述。
msg_id 是由服务端生成的全局唯一的消息 id,client_msg_id 是由客户端生产的在客户端范围内唯一的消息 id;client_msg_id 的设计是为了实现消息的幂等性,防止因不可靠网络导致消息的重复,在后面分析消息发送流程时将详细解读。
del_flag 是消息的删除标记,实现消息的定制化需求。
和 “联系人表” 类似,to_uid 是主属性,其他所有字段都是围绕 to_uid 进行描述的。当云消息记录数量非常高时,通过 to_uid 作为分表索引字段对云消息表进行分表,保证同一用户的所有消息记录能落在同一张表中。
最后,总结文中关键:
1、离线消息表用来存储用户离线时的消息记录,用户登录后从该表中拉取所有离线记录;
2、联系人表存储好友关系,每一条联系人关系,需要分别站在双方维度分别存储一条记录,实现联系人的定制化需求和分库分表需求;
3、云消息表存储所有历史记录消息,每发送一条消息,需要分别站在消息收发双方维度分别存储一条记录,实现云消息的定制化需求和分库分表需求。
作者:棕生 | 来源:公众号——架构之魂
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。