专业IM即时通讯软件开发,值得信赖!

IM群聊消息已读未读功能如何实现

即时通讯软件开发 云聊IM 174℃

IM系统中,特别是在ToB环境,消息的阅读状态或许是一个强需求。本文从服务端角度来探讨阅读状态。能力有限,权当个人笔记,欢迎交流。

对于私聊,消息的阅读状态比较容易实现,在性能和存储上也不存在问题。

但对于群聊,考虑到存储和处理性能,特别当处于一个云环境时,如何高效处理群聊的阅读状态是一个非常值得探讨的话题。

这里提到的“高效”含3个方面:存储空间、处理速度、传输字节数,本文着重探讨存储空间。

阅读状态交互流程

从IMS角度,笼统地理解客户端包含App和sdk,如没有特别说明,均不做区分。
发送者发送的消息,在接收者阅读消息后,是否要求阅读者通知已读,可能是由系统配置、组织配置、群组配置等决定,也可能由发送者根据业务需求决定。以下的讨论,均假设消息需要阅读状态。

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

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

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

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

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

重点说下:向已读列表的消息的原始发送者通知消息已读

私聊的场合,比较好理解,就是发送给私聊的对方;

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

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

消息的发送者,加载消息列表到聊天窗口时,可能需要展示消息是否被已读;对群聊而言,显示的信息可能是n人未读的提示,那么需要向服务端查询消息的未读人数,由于客户端可能在UI显示自己发出的多条消息,需支持一次请求查询多条消息。

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

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

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

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

探讨如何存储阅读状态

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

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

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

本章讨论,以假设群成员有640人为前提

方案1

每一条消息都维护:

  1. 接收人员列表receiver_list
  2. 已读人员列表read_list

IMS收到一条消息时,用全体群成员构建receiver_list

IMS收到群成员对这条消息的已读通知时,将此成员加入到read_list

当客户端需要获取此消息的未读人数时,用receiver_list的个数减去read_list的个数;

当客户端需要获取此消息的已读、未读人员列表时,需用receiver_list减去read_list得到未读人员列表。

案1每条消息的存储空间是:

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

方案2

每一条消息维护:

  1. 未读人员列表unread_list
  2. 已读人员列表read_list

IMS收到一条消息时,用全体群成员构建unread_list;

IMS收到群成员对这条消息的已读通知时,将此成员从unread_list移出,同时加入到read_list;

当客户端需要获取此消息的未读人数时,直接计算unread_list的个数;

当客户端需要获取此消息的已读、未读人员列表时,直接返回unread_list和read_list。

案2每条消息的存储空间是:

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

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

方案3

探讨3.1节、3.2节的不足

3.1节、3.2节这两种案,都能满足功能需求,但存在巨大的存储浪费。

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

  • 3.1方法: 1024 * (640 * 4 + 已读人数 * 4),范围是 2.5MB ~ 5MB,
  • 3.2方法: 1024 * 640 * 4,等于2.5MB

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

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

如何减少存储空间

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

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

假定平均每1024条消息共用一个群成员列表,发了1024条消息后,群成员变化了,此后需要用新的群成员列表。那么这一千条消息的阅读状态所占用的空间是:

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

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

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

当一条消息没有人已读时,阅读状态占用0字节;当群内每个人都阅读时,占用的空间最大,即640 / 32 = 20字节。
因此优化之后,这一千条消息的阅读状态所占用的空间,范围是2.5KB ~ (2.5KB + 1024 * 20B),即2.5KB ~ 22.5KB,此数值与3.1节、3.2节对比,有了极大幅度地下降。

该表格的前提条件:

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

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

如何提高阅读状态的处理速度

小宝往公司群发了一条消息我来给大家介绍一下新来的女同事,大家立即、马上、瞬间、闪电般地查看消息,感觉迟1秒就会失去秒杀女神的机会一样,意味着一瞬间会有N多条已读通知发送到IMS。对这些消息的处理流程是一样的,可合并这些操作以批量形式进行存储、转发。

由于存储消息的阅读状态是一个设置bit的过程,所以不存在互斥的问题,即使在分布式环境也可以放心操作。

消息对应的成员列表信息可临时缓存在内存对象内,以减少查询IO,提高效率。

阅读状态的通讯交互

虽然服务端采用人员清单和阅读状态分离的设计,但与客户端(不区分App和SDK)通讯时,考虑到降低客户端设计的复杂度,在第2章提到通讯过程不引入这种分离设计。

喜欢 (16)
仿微信聊天软件开发
点击这里给我发消息