默认
打赏 发表评论 21
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
IM群聊消息的已读未读功能在存储空间方面的实现思路探讨
阅读(208376) | 评论(21 收藏3 淘帖1 2
微信扫一扫关注!

本文由作者“fzully”授权发布,他的博客地址是:blog.csdn.net/fzrlly。即时通讯网收录时,有较大范围修订和改动,感谢原作者的分享。


1、引言


IM系统中,特别是在企业应用场景下,消息的已读未读状态是一个强需求。

以阿里的钉钉为例,钉钉的产品定位是用于商务交流,其“强制已读回执”功能,让职场人无法再“假装不在线”、“假装没收到”。更有甚者,钉钉的群聊“强制已读回执”功能,甚至能够知道谁读了消息,谁没有读消息(老板的福音啊)。

IM群聊消息的已读未读功能在存储空间方面的实现思路探讨_11.jpg
▲ 钉钉里的群聊消息已读未读功能效果

功能看起来很酷,但用起来是一言难尽(上班族心里苦.... )。实际上,技术实现也并不容易。

那么,对于已读未读状态:

  • 1)如果是私聊:消息的阅读状态比较容易实现,在性能和存储上也不存在问题;
  • 2)如果是群聊:考虑到存储和处理性能,特别当处于一个云环境时,如何高效地处理群聊的已读未读状态是一个非常值得探讨的话题。

这里提到的“高效”含3个方面:

  • 1)存储空间;
  • 2)处理速度;
  • 3)传输字节数。

本文将从服务端的角度来探讨已读未读状态,在具体的技术实现上对于存储空间占用方面的思路差异。能力有限,权当个人笔记,欢迎交流。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:
IM群聊消息的已读未读功能在存储空间方面的实现思路探讨_52im_qr_即时通讯技术圈_400px.png
▲ 本文在公众号上的链接是:https://mp.weixin.qq.com/s/yUkKPOBsdqLlxiFrGmwFRQ

2、内容点评


在收录本文前,Jack Jiang建议原作者对某些具体的技术点进行更深入的分享,但因作者工作较忙,本文中的某些关键技术点未来的及作进一步展开。

所以,本文可以作为IM聊天消息(主要是群聊)中已读未读功能的基本实现思路方面的参考,但不建议盲目迷信文中的结论或方案,避免被一些不够具体的技术指标而误导

3、相关文章


如果你还想了解更多有关IM群聊中已读未读功能的实现逻辑,可以进一步阅读干货文章《IM群聊消息的已读回执功能该怎么实现?》(强烈推荐)。

如果你对IM中的已读未读功能有产品方面的痛点困惑,可以参考一下微信对已读未读功能的设计定位,详见《IM热门功能思考:为什么微信里没有消息“已读”功能?》。

更多IM群聊技术方面的文章详见文本附录部分。

4、本文作者


IM群聊消息的已读未读功能在存储空间方面的实现思路探讨_author.jpg
fzully(柳林勇):2005年数学系毕业,先后就职于福建新大陆、福建富士通、北京世纪奥通。长期从事服务端软件开发,涉及SIP服务器、内核RTP转送、电信级AAA认证系统、IM即时通讯系统等。在分布式高性能系统设计有多年经验积累。

本作者的另一篇:《IM开发干货分享:如何优雅的实现大量离线消息的可靠投递》也已被即时通讯网收录并整理发布,有兴趣可以前往阅读。

5、已读未读状态交互流程


发送者发送的IM聊天消息,在接收者阅读消息后,是否要求阅读者通知已读,可能是由系统配置、组织配置、群组配置等决定,也可能由发送者根据业务需求决定。以下的讨论,均假设消息需要已读未读状态。

客户端与服务端之间,关于阅读状态的命令只需3个,每个命令含请求和应答。

5.1通知消息已读(私聊、群聊通用)


当小宝阅读了一条或若干条消息,需向服务端发送消息已读通知:“众爱卿发的x+y+z消息,朕已阅”。

服务端收到小宝的已读通知时,需完成以下事项:

  • 1)存储消息的已读状态;
  • 2)返回应答给小宝;
  • 3)向已读列表的消息的原始发送者通知消息已读。

对于第“3)”步:

  • 1)私聊的场合,比较好理解,就是发送给私聊的对方;
  • 2)群聊的场合,可很不一样:因为小宝发送的已读消息列表,可能是由众爱卿发送的。考虑这种假设:张三、李四、王五发出的群聊消息,被小宝一下都阅读了,那么小宝发出的已读通知包含的消息列表,需要被IMS分解成3个已读通知(3个不同的消息列表),分别通知给张三、李四、王五,通知内容是“爱卿(不含'"众")发的这些消息,朕已阅”。

下面是大致的逻辑流程图:
IM群聊消息的已读未读功能在存储空间方面的实现思路探讨_22.jpg

5.2查询消息的未读人数(私聊、群聊通用)


消息的发送者,加载消息列表到聊天窗口时,可能需要展示消息是否被已读。

对群聊而言,显示的信息可能是n人未读的提示,那么需要向服务端查询消息的未读人数,由于客户端可能在UI显示自己发出的多条消息,需支持一次请求查询多条消息。

以未读人数的方式来表示消息的阅读状态,统一了私聊、群聊的查询,使得客户端-服务端间的接口更简单,同时使客户端的实现逻辑更统一。

就像下面这样:

  • 1)对于私聊:如果未读人数n>0,表示消息未读;
  • 2)对于群聊:直接显示n人未读即可,当然,当n等于0时表示全部已读。

5.3查询群消息的已读、未读人员清单(群聊)


当客户端希望显示某一条群聊消息的已读、未读人员列表,需向服务端发起查询。

大致的逻辑流程图如下:
IM群聊消息的已读未读功能在存储空间方面的实现思路探讨_33.jpg

6、几种具体的已读未读状态存储思路探讨


6.1基本约定


群聊的阅读状态比私聊复杂,因此这里着重讨论群聊的阅读状态。

假设群成员数是n,各个客户端立即IM服务端发送已读通知。服务端需存储每个人的阅读状态,包括那些未读的成员。由于群的成员清单可能变化,比如今天增加了一个成员,则昨天发的消息、与今天发的消息,其接收者列表不一样。

即:

  • 1)同一个群的不同消息,对应的接收者列表可能不一样。
  • 2)换言之,每一条消息都需要记录完整的接收者列表和已读人员列表。

为了方便讨论,本章假设群成员有640人为前提。

6.2存储思路1


每一条消息都维护:

  • 1)接收人员列表receiver_list;
  • 2)已读人员列表read_list。

具体是:

  • 1)IM Server收到一条消息时,用全体群成员构建receiver_list;
  • 2)IM Server收到群成员对这条消息的已读通知时,将此成员加入到read_list。

客户端获取此消息的数据:

  • 1)当需要获取未读人数时,用receiver_list的个数减去read_list的个数;
  • 2)当需要获取已读、未读人员列表时,需用receiver_list减去read_list得到未读人员列表。

那么,思路1每条消息的存储空间是:

640个ID + 不定数量的已读人员ID



6.3存储思路2


每一条消息维护:

  • 1)未读人员列表unread_list;
  • 2)已读人员列表read_list。

具体是:

  • 1)IM Server收到一条消息时,用全体群成员构建unread_list;
  • 2)IM Server收到群成员对这条消息的已读通知时,将此成员从unread_list移出,同时加入到read_list。

客户端获取此消息的数据:

  • 1)当需要获取未读人数时,直接计算unread_list的个数;
  • 2)当需要获取已读、未读人员列表时,直接返回unread_list和read_list。

那么,思路2每条消息的存储空间是:

未读人员ID + 已读人员ID,合计640个ID


思路2的实现,占用的空间是案1的0.5倍~1.0倍。即案2占用的空间少,但在每次收到客户端的已读通知时,比案1多了一个操作:从unread_list进行减员。

6.4存储思路3(我的实现)


5.4.1)探讨6.2节、6.3节的不足:

6.2节、6.3节这两种思路,都能满足功能需求,但存在巨大的存储浪费。

该群有640人,如果群内聊天每天有1024条消息,人员ID以4字节存储计算,那么为该群每天的消息阅读状态需要消耗的空间是:

  • 5.2节思路1:1024 * (640 * 4 + 已读人数 * 4),范围是 2.5MB ~ 5MB;
  • 5.3节思路2:1024 * 640 * 4,等于2.5MB。

这仅仅是一个群在一天之内产生的阅读状态数据,如果是在云平台运行,单此功能消耗的空间,呵呵~~

题外话:如果成员不是用4字节整型存储,而改用字符串,比如"1123356777",那就更可观了。

5.4.2)如何减少存储空间:

考虑群成员并非时时刻刻都在变化,多数情况下,群成员的列表是相对稳定的,今天的和上周(甚至更久以前)的列表甚至可能是一样的,那么有可能几百条消息,甚至几万条消息对应的群成员列表是相同的。

因此,引出本文的重点思想:

考虑让不同的消息共用群成员列表,即把消息的阅读状态与群成员列表分开存储,并记录它们之间的关联。


假定平均每1024条消息共用一个群成员列表,发了1024条消息后,群成员变化了,此后需要用新的群成员列表。

那么这一千条消息的阅读状态所占用的空间是:

群成员列表空间 + 1024条消息的阅读状态:640 * 4 + 1024 * 每条消息的阅读状态所占空间


在具备群成员列表的前提下,如何减少每条消息的阅读状态所占空间?

很自然会想到用bit来表示已读人员,因为一个32位整型可表示32个人的已读状态。bit的顺序只需与群成员列表的顺序一致即可。


当一条消息没有人已读时,阅读状态占用0字节;当群内每个人都阅读时,占用的空间最大,即640 / 32 = 20字节。

因此优化之后,这一千条消息的阅读状态所占用的空间,范围是2.5KB ~ (2.5KB + 1024 * 20B),即2.5KB ~ 22.5KB,此数值与5.2节思路1、5.3节思路2对比,有了极大幅度地下降。

如下图所示:
IM群聊消息的已读未读功能在存储空间方面的实现思路探讨_44.jpg

该表格的前提条件:

  • 1)一个群有640人;
  • 2)该群连续1024条消息对应的群成员列表是稳定的。

退一步考虑,哪怕这1024条消息对应的群成员列表不稳定,中间变化了10次,那么也仅会多出2.5KB * 10即25KB的存储空间,与案1、案2相比仍然有极大优势。

7、如何提高已读未读状态的处理速度


小宝往公司群发了一条消息我来给大家介绍一下新来的女同事,大家立即、马上、瞬间、闪电般地查看消息,感觉迟1秒就会失去秒杀女神的机会一样,意味着一瞬间会有N多条已读通知发送到IMS。

对这些消息的处理流程是一样的:

  • 1)可合并这些操作以批量形式进行存储、转发;
  • 2)由于存储消息的阅读状态是一个设置bit的过程,所以不存在互斥的问题,即使在分布式环境也可以放心操作;
  • 3)消息对应的成员列表信息可临时缓存在内存对象内,以减少查询IO,提高效率。

附录:更多IM群聊技术文章


快速裂变:见证微信强大后台架构从0到1的演进历程(一)
如何保证IM实时消息的“时序性”与“一致性”?
IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?
IM群聊消息如此复杂,如何保证不丢不重?
微信后台团队:微信后台异步消息队列的优化升级实践分享
移动端IM中大规模群消息的推送如何保证效率、实时性?
现代IM系统中聊天消息的同步和存储方案探讨
关于IM即时通讯群聊消息的乱序问题讨论
IM群聊消息的已读回执功能该怎么实现?
IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?
一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践
[技术脑洞] 如果把14亿中国人拉到一个微信群里技术上能实现吗?
IM群聊机制,除了循环去发消息还有什么方式?如何优化?
网易云信技术分享:IM中的万人群聊技术方案实践总结
阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处
IM群聊消息的已读未读功能在存储空间方面的实现思路探讨
>> 更多同类文章 ……

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

上一篇:分享我自已写的基于netty和webrtc的IM聊天系统,一起学习下一篇:零基础IM开发入门(一):什么是IM系统?

本帖已收录至以下技术专辑

推荐方案
评论 21
一个群,给用户生成单独的群ID,,基于 群ID ,做消息一读未读,是不是占用字节数更少。例如:

我这里以一个群最大 999人为例来说明思路。
群ID: 1111111
群中的成员按照入群的顺序,生成ID 为: 1111111 0001  

谁读了没读,只通过 23 个字节的位置索引来表示是那个人,就可以表示一条消息状态。
一个人都在加入了那些群,冗余保存群ID及在群中的ID。


引用:laojichuxin 发表于 2020-06-29 15:08
一个群,给用户生成单独的群ID,,基于 群ID ,做消息一读未读,是不是占用字节数更少。例如:

我这里以 ...

我感觉把问题搞复杂了,位运算,不管从哪个角度来说,都很难以维护
签名: 《能Ping通,TCP就一定能连接和通信吗?》http://www.52im.net/thread-4756-1-1.html
引用:JackJiang 发表于 2020-06-29 15:43
我感觉把问题搞复杂了,位运算,不管从哪个角度来说,都很难以维护

是有点计算换空间了,消息上,携带一个数,作为已读未读的标识数字。

各有优缺点。
引用:laojichuxin 发表于 2020-06-29 17:49
是有点计算换空间了,消息上,携带一个数,作为已读未读的标识数字。

各有优缺点。

我觉得文章中作者太过于追求存储空间的优化了,点到为止即可,从可维护性、易用性、硬件成本上找到平衡是最好的,硬件成本上来说存储已经不是很贵了
签名: 《能Ping通,TCP就一定能连接和通信吗?》http://www.52im.net/thread-4756-1-1.html
引用:JackJiang 发表于 2020-06-29 22:16
我觉得文章中作者太过于追求存储空间的优化了,点到为止即可,从可维护性、易用性、硬件成本上找到平衡是 ...

赞成
引用:JackJiang 发表于 2020-06-29 22:16
我觉得文章中作者太过于追求存储空间的优化了,点到为止即可,从可维护性、易用性、硬件成本上找到平衡是 ...

说的很对啊,用位来维护其实很麻烦,是一个很好的解决思路,但不是一个切实落地的方案
引用:laojichuxin 发表于 2020-06-29 15:08
一个群,给用户生成单独的群ID,,基于 群ID ,做消息一读未读,是不是占用字节数更少。例如:

我这里以 ...

同一个群,上周的人员列表,在本周可能变了哦
好文章 学到了
学习了,非常好的思路。
但是也是感觉使用位运算是省空间,但是从可维护上来说确实有问题。
看来方案基本受认可,就是觉得位操作维护麻烦。
给大家个白嫖的机会,公开关联的代码,等整理完再发(<=2天),敬请期待!
引用:fzully 发表于 2020-09-07 16:17
看来方案基本受认可,就是觉得位操作维护麻烦。
给大家个白嫖的机会,公开关联的代码,等整理完再发(

等你
签名: 《能Ping通,TCP就一定能连接和通信吗?》http://www.52im.net/thread-4756-1-1.html
bit位在阅读状态的使用.zip (4.05 KB , 下载次数: 114 )
附件给出了bit位如何用在阅读状态,给出了关键实现,而不是全部源码。配合Jack一楼的文字介绍,更好理解。
注:目的是说明 位 怎么用在阅读状态,而不是要列出全部实现
引用:fzully 发表于 2020-09-08 23:13
附件给出了bit位如何用在阅读状态,给出了关键实现,而不是全部源码。配合Jack一楼的文字介绍,更好理解 ...

赞楼主,文字骚,技术也骚!
签名: 《能Ping通,TCP就一定能连接和通信吗?》http://www.52im.net/thread-4756-1-1.html
如果每个会话的消息是有顺序的,那已读状态就是记录会话中的每个人已读的最大值;这样群成员的已读状态就会形成离散的分布,那对于当前消息的已读状态,就是计算群成员中有多少人的已读值大于该消息的值
引用:一个菠萝 发表于 2020-10-20 09:50
如果每个会话的消息是有顺序的,那已读状态就是记录会话中的每个人已读的最大值;这样群成员的已读状态就会 ...

消息顺序,从理论上是很难百分百保证的,所以任何依赖于顺序的逻辑,都存在因乱序而导致的不正确性
签名: 《能Ping通,TCP就一定能连接和通信吗?》http://www.52im.net/thread-4756-1-1.html
引用:JackJiang 发表于 2020-10-20 11:03
消息顺序,从理论上是很难百分百保证的,所以任何依赖于顺序的逻辑,都存在因乱序而导致的不正确性

很多分布式id方案都可以解决id的有序性
引用:一个菠萝 发表于 2020-10-20 12:06
很多分布式id方案都可以解决id的有序性

id当然可以有序,但消息本身可能会乱序到达
签名: 《能Ping通,TCP就一定能连接和通信吗?》http://www.52im.net/thread-4756-1-1.html
引用:一个菠萝 发表于 2020-10-20 09:50
如果每个会话的消息是有顺序的,那已读状态就是记录会话中的每个人已读的最大值;这样群成员的已读状态就会 ...

用户读了某条消息,不能简单认定该会话的在这条消息之前的所有消息都是已读的。
用户可能跳着阅读,比如有好几十条消息,他跳最前面,从旧往新查看,但可能没看完整。没看到就是未读。
即,阅读状态是针对每一条消息的。
引用:fzully 发表于 2020-12-25 00:08
用户读了某条消息,不能简单认定该会话的在这条消息之前的所有消息都是已读的。
用户可能跳着阅读,比如 ...

有个现象是:常用im的聊天软件中,就算是跳着看的,下次再看该会话也是没有未读计数显示在该会话上的;对于那些连续好多未读的群消息会话,其已读属性是不是没有那么重要了,转变为是否看到这个会话有无未读来的更人性化些,毕竟未读消息越多,其即时消息的即时属性就越小,其及时性是不是已经没有那么重要了,换句话说:用户是不是专注于【即时】消息的已读更好一点,时间跨度过长,已然失去了即时性;而且如果真的是要观测每个人对该条消息是否已读,发送特殊的群消息类型是不是更好一点
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部