如果使用过钉钉,会发现你发出一条消息,消息下方会显示有几人未读(如下图),而且这个数字数字随着群里成员阅读消息会不断变化(减少),点击能够查看具体哪些人读了消息,哪些人未读消息。
企业微信里也有类似功能,叫做回执消息(本后后续称这类能知道对方看没看的消息为“回执消息”)
这类消息怎么实现的呢?
直观感觉,对方阅读消息后给消息发送者发送一条消息已读的确认消息即可实现该功能(怎么发送一条消息请参看《一个海量在线用户即时通讯系统(IM)的完整设计》)。但事实远飞这么简单,这样实现存在一些明显的挑战。
1、如果群人数较多(公司有些群超过2000人),一条消息发出,返回的已读确认消息上千条,会直接把发送者的手机推死,流量、电量也消耗不起。
2、已读未读人数,只有发送者查看这条消息的时候才关心。发送者如果在App上做别的事情,根本不需要关心当前有多少人已读。因此直接推送已读确认也不合适。
如果变为客户端查看的时候主动拉取呢?主动拉取同样存在一些挑战
1、主动拉取如果拉取时间间隔过大,已读未读人数更新不及时
2、时间间隔太小,消耗流量、电量
IM系统的特点是推拉结合,采用推拉结合的方案会不会更好呢?
具体做法如下
1、客户端打开会话,查看回执消息时,通过短连接向服务端拉取未读人数。
2、同时客户端向服务端请求订阅该条消息的回执消息(退出这个会话取消订阅)
3、服务端收到此消息的已读确认消息,向用户推送
这样看似较完美,实际上仍然面临推消息的挑战。当然我们可以按时间段进行消息聚合推送(类似map-reduce过程)来缓解消息过多的问题,比如,每2秒钟推送一次人数变化。
然而,消息订阅模式,需要服务端提供大量的资源来支持海量消息的订阅,服务端的实现复杂度也会大大提高。
这个方式也不可行!
我们注意到这类消息用户实际使用场景,用户只在需要查看这条消息已读情况的较短时间内关心已读未读数量。
因此设置一个合理的主动拉取策略就可以比较好的解决这个问题,整个流程如下图。
1、User1发出一条回执消息,其他用户(User2、User3……UserN)读取消息后,向服务端发送已读确认消息。服务端进行未读人数计算,并缓存
2、User1在查看回执消息时,主动拉取已读人数或未读人数
主动拉取策略怎么设置呢?
用户查看回执消息时,20秒之内,每2秒拉取一次;如果用户退出会话则停止拉取。
如果用户长时间停留在这条回执消息,拉取未读人数的频率会随着时间衰减,不会造成流量和电量的浪费。
这个策略在功能、性能、实现复杂度之间求得一个较好的平衡!