本文由阿里闲鱼技术团队景松分享,原题“到达率99.9%:闲鱼消息在高速上换引擎(集大成)”,有修订和改动,感谢作者的分享。
在2020年年初的时候接手了闲鱼的IM即时消息系统,当时的消息存在各种问题,网上的用户舆情也是接连不断。
我们调研了集团内的一些解决方案,例如钉钉的IMPass。如果贸然直接迁移,技术成本和风险都是比较大的,包括服务端数据需要双写、新老版本兼容等。
那么基于闲鱼现有的即时消息系统架构和技术体系IM电竞,如何来优化它的消息稳定性、可靠性?应该从哪里开始治理?当前系统现状到底是什么样?如何客观进行衡量?希望本文能让大家看到一个不一样的闲鱼即时消息系统。
PS:如果您对IM消息可靠性还没有概念,建议先阅读这篇入门文章《零基础IM开发入门(三):什么是IM系统的可靠性?》。
- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》- 开源IM框架源码:
PS:可能很多人对移动网络的复杂性没有一个系统的认知,以下文章有必要系统阅读:
对于发送者来说,它不知道消息是否有送达,要想做到确定送达,就需要加一个响应机制IM电竞。
上面流程看似简单,关键是中间有个服务端转发过程,问题就在于谁来回这个确认信息,以及什么时候回这个确认信息。
正如上述模型所示:一个可靠的消息投递机制就是靠的6条报文来保证的,中间任何一个环节出错,都可以基于这个request-ack机制来判定是否出错并重试。
我们最终采用的方案也正是参考了上面这个模型,客户端发送的逻辑是直接基于htIM电竞tp的所以暂时不用做重试,主要是在服务端往客户端推送的时候,会加上重试的逻辑。
在解决消息可靠投递这个问题之前,我们肯定首先应该搞清楚当前面临的具体问题到底有哪些。
然而在接手这套即时消息系统时,并没有相关的准确数据可供参考,所以当前第一步还是要对这套消息系统做一个完整的排查,于是我们对消息做了全链路埋点。
这次整个数据的统计都是基于埋点来做的,但在埋点的过程中发现了一个很大的问题:当前这套即时消息系统没有一个全局唯一的消息ID。这导致在全链路埋点的过程中,无法唯一确定这条消息的生命周期。
1)SessionID: 当前会线)SeqID:用户当前本地发送的消息序号,服务端是不关心此数据,完全是透传;
3)Version:这个比较重要,是消息在当前会话中的序号,以服务端为准,但是客户端也会生成一个假的version。
以上图为例:当A和B同时发送消息的时候,都会在本地生成如上几个关键信息,当A发送的消息(黄色)首先到达服务端,因为前面没有其他version的消息,所以会将原数据返回给A,客户端A接收到消息的时候,再跟本地的消息做合并,只会保留一条消息。同时服务端也会将此消息发送给B,因为B本地也有一个version=1的消息,所以服务端发过来的消息就会被过滤掉,这就出现消息丢失的问题。
当B发送消息到达服务端后,因为已经有version=1的消息,所以服务端会将B的消息version递增,此时消息的version=2。这条消息发送给A,和本地消息可以正常合并。但是当此消息返回给B的时候,和本地的消息合并,会出现2条一样的消息,出现消息重复,这也是为什么闲鱼之前总是出现消息丢失和消息重复最主要的原因。
当前消息的推送逻辑也存在很大的问题,发送端因为使用http请求,发送的消息内容基本不会出问题,问题是出现在服务端给另外一端推送的时候。
服务端在给客户端推送的时候,会先判断此时客户端是否在线,如果在线才会推送,如果不在线就会推离线消息。
长连接的状态如果不稳定,导致客户端真实状态和服务端的存储状态不一致,就导致消息不会推送到端上。4.4 客户端逻辑问题
我们也调研了钉钉的方案,钉钉是服务端全局维护消息的唯一ID,考虑到闲鱼即时消息系统的历史包袱,我们这边采用UUID作为消息的唯一ID,这样就可以在消息链路埋点以及去重上得到很大的改善。
在新版本的APP上面,客户端会生成一个uuid,对于老版本无法生成的情况,服务端也会补充上相关信息。
消息的ID类似于 a1a3ffa118834033ac7a8b8353b7c6d9,客户端在接收到消息后,会先根据MessageID来去重,然后基于Timestamp排序就可以了,虽然客户端的时间可能不一样,但是重复的概率还是比较小。
基于本文“3、行业方案”一节中的重发重连模型,我们完善了服务端的消息重发的逻辑、客户端完善了断线重连的逻辑。
现有消息系统中,很多复杂情况是通过在业务层增加兼容代码来解决的,消息的数据同步就是一个很典型的场景。
在完善数据同步的逻辑之前,我们也调研过钉钉的一整套数据同步方案,他们主要是由服务端来保证的,背后有一个稳定的长连接保证。
我们的服务端暂时还没有这种能力,所以闲鱼这边只能从客户端来控制数据同步的逻辑。
会话和消息,会话又分为:虚拟节点、会话节点和文件夹节点。在客户端会构建上图一样的树,这棵树主要保存的是会话显示的相关信息,比如未读数、红点以及最新消息摘要,子节点更新,会顺带更新到父节点,构建树的过程也是已读和未读数更新的过程。
就是域环是有长度的,最多保存256条,当用户的消息数多于256条,只能从数据库中读取。
关于服务端的存储方式,我们也调研过钉钉的方案——是写扩散,优点就是可以很好地对每位用户的消息做定制化,缺点就是存储量很很大。
我们的这套解决方案im新闻,应该是介于读扩散和写扩散之间的一种解决方案。这个设计方式不仅使客户端逻辑复杂,服务端的数据读取速度也会比较慢,后续这块也可以做优化。
在做客户端和服务端的全链路改造的同时,我们也对消息线上的行为做了监控和排查的逻辑。
该指标不区分离线在线,取用户当日最后一次更新设备时间,理论上当天且在此时间之前下发的消息都应该收到。
1)消息的安全性不足:容易被黑产利用,借助消息发送一些违规的内容;2)消息的扩展性较弱:增加一些卡片或者能力就要发版,缺少了动态化和扩展的能力。
3)底层的伸缩性不足:现在底层协议比较难扩展,后续还是要规范一下协议。
从业务角度看,消息应该是一个横向支撑的工具性或者平台型的产品,且可以快速对接二方和三方的快速对接。
接下来,我们会持续关注消息相关的用户舆情,希望闲鱼即时消息系统能帮助用户更好的完成业务交易。