@@ -4,4 +4,84 @@ draft = false
44title = ' 【IM专题】01-消息存储设计方案'
55+++
66
7- ![ 图片描述] ( /images/wxsync-2023-05-5661e69baff774c2c2c90b5baf71f18c.png )
7+ 即时通讯系统面对的核心挑战是海量消息的高效存储和可靠同步
8+
9+ ![ ] ( /images/1760555202788-1dced840-5ce4-46a3-bffc-3d09785e64fd.png )
10+
11+ 假设当前有一个群组A,里面有三个用户,此时uid=101的用户在群中发送了一条消息,消息该如何存储?
12+
13+ # 方案一:写扩散(Push Model / 推送模型)
14+ 对数据进行写入操作时,有更多的写入动作。每发送一条群消息,系统需要站在每个群成员的维度分别保存一条消息记录。
15+
16+ ![ ] ( /images/1760561205294-df9183ab-e468-491e-aa3a-6bfebf01e421.png )
17+
18+ 写扩散的好处:
19+
20+ 1 . 便于读取:避免复杂的合并与排序。
21+ 2 . 方便消息的定制化处理:若一个群成员删除了群消息,只是删除了自己的群消息,并不会影响到其他群成员能浏览到该条群消息;方便支持消息的“已读”状态独立标记。
22+ 3 . 便于分库分表:每一个用户的所有群消息能落在同一张表中,避免对所有数据表的遍历。
23+
24+
25+
26+ 适用场景:私聊、小群聊(群成员数量少)。
27+
28+ 主要限制:当群成员数量非常多时,写扩散模型会因为冗余写入导致极高的写延迟和系统写操作压力。
29+
30+
31+
32+ # 方案二:读扩散(Pull Model / 拉取模型)
33+ 对数据进行读取操作时,有更多的读取动作。每发送一条群消息,只在库中存入一条共享消息记录。
34+
35+
36+
37+ 读扩散的优势:
38+
39+ 1 . 高写入效率
40+ 2 . 实时修改和权限控制更简单:消息如果需要修改或撤回,只需要update一条记录
41+
42+ 缺点:
43+
44+ 1 . 读取压力大:
45+ + 多源查询和排序的开销:当用户拉取全局未读消息列表时,需要依次遍历自己所有活跃会话的Timeline,以获取每个会话的最新一条消息。
46+ + 全局排序:在取到所有最新消息后,必须按照消息时间戳进行全局排序,才能按时间顺序对主会话列表进行排序和展示,这是一个CPU密集型操作,极大地增加了读取延迟。
47+ 2 . 消息定制化处理困难:因为消息是共享存储的,任何针对消息状态的个性化处理都需要引入额外的存储和逻辑处理。例如,删除自己的消息记录,需要引入一个独立的状态表,存储“用户-会话-消息”的删除标记;消息已读标记,可以引入一个已读游标,记录用户在某个会话中已读到的最新消息ID。
48+
49+
50+
51+ # 扩展
52+ 读写扩散模型除了 IM 之外,应用更多的是 feed 流场景,比如:微信朋友圈、微博等。
53+
54+ 在完整的读写扩散模型中,有【订阅】、【发布】、【取消订阅】等主要的业务动作
55+
56+
57+
58+ ## 1 订阅
59+ 假设一个系统中有 X、Y、Z 三个用户,用户 X 订阅了 用户 Y 和用户 Z,用户 Y 订阅了用户 Z,那么其订阅和被订阅的关系存储,见下图。
60+
61+ ![ ] ( /images/1760561368890-ea9aeef6-d031-49f8-b4eb-d8022591dcfb.png )
62+
63+ 订阅与被订阅关系,一般通过双向的 KList 结构来存储。
64+
65+ 用户 X 订阅了 Y 和 Z,那么在 “订阅关系” 中,Key 是 X,List 中元素包括 Y 和 Z;用户 Y 订阅了 Z,那么在 “订阅关系” 中,Key 是 Y,List 中元素包括 Z。
66+
67+ 在 “被订阅关系” 中,Y 被 X订阅,则 Key 是 Y,List 中元素包括 X;Z 被 X 和 Y 订阅,则 Key 是 Z,List 中元素包括 X 和 Y。
68+
69+ ## 2 发布
70+ 假设用户 Z 发布了两条消息:msg1 和 msg2,在不同的扩散模型中,则有不同的存储方式。
71+
72+ 在写扩散模型中,处理方式为:
73+
74+ + 从被订阅关系中,由 Z 获取元素 X 和 Y;
75+ + 在 “写扩散存储” 中,将两条消息 msg1 和 msg2 分别写入到 X 和 Y 的 KList 结构中,见下图;
76+ + 用户 X 和 Y 在拉取消息时,直接从 “写扩散存储” 中读取。
77+
78+ ![ ] ( /images/1760561368914-081c43bb-cc74-4dd3-a2e3-7ccaf578ace7.png )
79+
80+ 在读扩散模型中,处理方式为:
81+
82+ + 在 “读扩散存储” 中,直接将两条消息 msg1 和 msg2 写入到 Z 的 KList 结构中,见下图;
83+ + 用户 X 在拉取消息时,首先从 “订阅关系” 中获取其订阅的列表,Y 和 Z;
84+ + 然后再从 “读扩散存储” 中,分别获取 Y 和 Z 的消息列表后进行聚合。
85+
86+ ## 3 取消订阅
87+ 取消订阅操作比较简单,直接从 “订阅关系” 和 “被订阅关系” 的存储列表中删除相关元素,同时根据业务规则,对已经获取的消息是否需要保留进行判断即可。
0 commit comments