目录
在 IM 系统中,“未读数” 是一个非常核心的概念。
首先,从产品体验上,用户登录之后,往往是由消息 “未读数” 引导用户后续的操作;其次,“未读数” 实现逻辑直接影响了上层业务的核心设计;轻量级的未读数模型,会大大降低服务端负载,提升性能,减少损耗。
IM 的未读数模型包括 “离线存储模型” 和 “在线存储模型”,我们今天结合场景深入分析。
一、离线存储模型
所谓未读数的离线存储模型,是指消息未读数的统计和计算需要借助于离线消息库,见下图场景。
-
当用户 uid=101 发一条消息给离线用户 uid=102 时,处理的逻辑是向 “离线库” 写入一条记录,向 “云消息库” 写入两条记录;
-
当用户 uid=101 发一条消息给在线用户 uid=102 时,处理的逻辑是只向 “云消息库” 写入两条记录;
-
当用户 uid=102 登录后,会首先从 “离线库” 中拉取所有的离线消息记录到客户端本地,这些离线消息记录的条数是客户端未读数的一部分,还有一部分是已经在客户端本地但未被用户阅读的消息(这是客户端逻辑,不在本文讨论范围)。
可以这样简单来理解未读数的离线存储模型:
-
当用户处于在线状态时,客户端收到消息后,未读数的统计完全由客户端来计算;
-
当用户处于离线状态时,由离线库存储所有的离线消息,用户登录后,离线消息全部拉取到客户端,由客户端统计未读数。
未读数的离线存储模型,很容易理解,也方便落地实现,但是绝非轻量。
当用户有大量的离线消息时,尤其是用户有非常多的群消息时,哪怕用户对这些消息并不关注,在登录的时候也都需要将这些离线消息拉取到客户端本地;这让登录逻辑会很重,也消耗了客户端很多的网络带宽。
所以,离线存储模型适合于用户在线率比较高的 PC 时代的 IM 应用,并不适合移动互联网场景。
二、在线存储模型
在未读数的离线存储模型中,“离线库” 中的数据其实是 “云消息库” 中部分数据的冗余,所以完全可以舍弃 “离线库”,并通过在 “云消息库” 中进行标注来识别出 “离线” 或 “未读”,这就是未读数的在线存储模型。见下图场景。
未读数的在线存储模型,不关注消息是否是离线的,只关注发送到服务端的消息,有多少条是还未到达接收方客户端的,这里的 ”多少条“ 即消息未读数,由联系人表的一个扩展字段 unread 来负责存储。
-
用户 uid=101 发一条消息给用户 uid=102,不管用户 uid=102 是在线状态还是离线状态,服务端都需要向云消息库写两条记录,向联系人库更新两条记录;联系人库中,对于 uid=102 的记录,其 unread 字段需要执行加1的操作(unread++);
-
用户 uid=102 登录后,需要首先从联系人库中,读取其所有未读的消息数量,即 unread;可以对比离线存储模型,在线存储模型中只需要从服务端获取一个数值,而离线存储模型则需要获取所有的离线消息;
-
用户 uid=102 在未读数的引导下,会进入与好友的聊天窗口,通过不断滑动屏幕,触发拉取历史消息的操作,即服务端会从云消息库中分页读取历史消息;这一步是在用户的操作之下进行的,用户对哪个联系人感兴趣,就拉取哪个联系人的消息,按时间顺序由近及远,倒序拉取;
-
消息拉取到客户端本地后,需要对服务端联系人库中的 unread 字段进行重置;需要注意,不是将 unread 重置为 零;用户 uid=102 在与好友的聊天窗口中,将最新的一条消息记录的标识,即 last_msg_id ,作为参数传递到服务端,服务端根据 last_msg_id 查询云消息库,获取比 last_msg_id 更大的消息记录数,然后将此数来重置未读数字段 unread(大家思考一下,为什么需要这样操作);重置未读数的时机有两个:一是用户进入与好友的聊天窗口时,二是用户离开聊天窗口时。
这就是整个未读数的在线存储模型的基本逻辑,与离线存储模型相比,轻量很多;首先是用户登录时,只需要从服务端获取一个未读数值;然后根据用户操作,按需要拉取历史消息,这大大节省了用户的网络带宽,用户操作更轻便,体验更流畅。所以,未读数的在线存储模型更适合移动互联网场景下的 IM 应用。
为了方便大家,对未读数在线存储模型的核心逻辑理解更深刻,我们从数据表的视角再深入分析一下,见下图。
-
图中左侧是用户 uid=102 的云消息记录,共有 5 条,分别是 uid=101 和 uid=103 发送的;
-
图中右侧是用户 uid=102 的联系人记录,其联系人是 uid=103,该联系人曾发送 3 条消息给 uid=102,所以未读数是 unread=3;
-
任何一个用户发送消息给 uid=102 时,都需要执行两步操作:对云消息表追加记录,对联系人表更新未读数(unread++);
-
对于用户 uid=102,登录时触发 “拉未读数” 的操作,即从联系人表读取 unread,比如:拉取联系人 uid=103 的消息未读数是 3;
-
在未读数的引导下,用户 uid=102 进入与 uid=103 的聊天窗口中,通过滑动屏幕触发 “拉云消息” 的操作,即从云消息表中倒序分页拉取消息记录;
-
用户 uid=102 离开聊天窗口时触发 “重置未读” 的操作,假设 uid=103 发送的三条消息全部被 uid=102 拉取到了本地,则将最新的消息记录标识 last_msg_id=5 作为参数传递到服务端,服务端对云消息表执行 【select count(*) from 云消息表 where to_uid=102 and from_uid=103 and msg_id > 5】,然后将获取的结果 0 对联系人表执行 【update 联系人表 set unread=0 where to_uid=102 and from_uid=103】。
在未读数在线存储模型中,服务端需要提供三个最核心的接口:拉未读数、拉云消息、重置未读。
总结文中关键
-
IM 的未读数会引导用户操作,影响业务流程设计,其实现包括 “离线存储模型” 和 “在线存储模型”;
-
离线存储模型,其消息统计借助于离线消息库,用户登录后将所有离线消息拉取到客户端本地进行计算;离线存储模型适合用户在线率较高的 PC 时代的 IM 应用;
-
在线存储模型,其消息统计借助于联系人表的 unread 字段,用户登录后通过读取联系人表获取未读数,通过读取云消息表获取历史记录;在线存储模型适合移动互联网下的 IM 应用。