后台IM消息是闲鱼用户重要的交易咨询工具。有两个核心目标。确保用户的消息及时传递给收件人。IM消息根据接收方设备是否在线分为离线推送和在线推送。数据显示,闲鱼每天超过一半的IM消息是在线发送的,在线消息的到达率和及时性直接影响用户。在体验方面,本文将着重分析优化线上渠道的稳定性,确保用户消息及时到达。面临什么问题?在IM场景下,用户与云端通信频繁,为了实现用户消息的及时到达,云端推送消息到达用户。因此,当用户在线时,设备和云端会保持一个通信TCP长连接通道,可以更轻量级地与服务器进行交互。现代IM即时通讯的下行消息是通过长连接发送的。闲鱼消息使用ACCS长连接。ACCS是一种全双工、低延迟、高安全性的通道服务。但是,由于用户设备网络状态的不确定性,可能会出现各种网络异常,导致长连接通道中断。一旦长连接意外中断,用户将无法及时收到在线消息,因此我们需要尽量及时感知长连接断开并尝试重连。下推消息没有到达感知的长连接中断重连大部分时间只能保证长连接的有效性,但是下推消息客户端可能在长连接时根本收不到术语连接无效或不稳定。简单的说,就是重连机制并不能保证下行报文一定会到达。以下场景可能会导致下行消息失败:服务器长时间发送下行消息,连接畅通,消息的通道在传输路径上断开,客户端无法接收到设备在线状态.当客户端下载消息时,它认为设备在线。实际上是设备离线,客户端收不到下行消息。但是客户端后续处理失败,比如无法登录图书馆,也没有成功向用户展示消息,accs下行成功率在97%左右,不耐烦的同学会问,你丢了3%的消息了吗?不会,这3%的消息不会丢失,但不保证及时到达用户手中。我们的消息同步模型是推拉组合模式。用户拉取消息时,会拉取设备当前位置和服务器最新位置的所有消息。主动拉取消息的触发时机有限,主要有:用户冷启动应用,主动同步消息。用户主动下拉刷新应用。点处有间隙,触发同步。可见,上述主动同步消息的触发很大程度上取决于用户行为或是否收到新消息,很难保证消息及时到达。如果是用户经常打开的IM软件,问题不大,但闲鱼APP活跃度较低,有时甚至依赖IM消息激活,一条延迟的消息可能会导致用户错过。对于一笔交易,闲鱼新闻不允许这样拖延。基于以上分析,我们首先描述一个反映现状的数据指标。从上面的描述我们可以看出,并不是所有的accs消息都是被推送下来的,也有可能是被主动拉下来的。如果他们被推,他们必须及时到达。如果他们被拉动,则受到用户行为的限制。对于拉取的这部分消息,我们定义为ACCs消息补偿到达,然后计算ACCs消息补偿到达时间。消息范围仅限于下行成功但客户端主动拉取并同步消息的服务端ACC。在以前的版本中,这个数据是在60分钟左右。需要注意的是,这个数据并不是消息到达用户的耗时时间,因为如果达到了线上到线下的消息,拉取消息的时间取决于用户的行为(当用户打开应用时)),但这个数据也能大致反映在线消息到达延迟状态。接下来,本文将从长连接重连和未到达消息重发两个方面详细介绍我们如何优化在线渠道的稳定性。长连接与重连长连接为什么会中断?每一个原因都必须有一个结果。我们先分析一下是什么原因导致连接中断。可能的原因有:用户设备与网络断开连接。不稳定设备网络正常,TCP连接因NAT超时被运营商中断。如果由于用户操作导致网络状态改变,会有网络状态改变事件通知。在这种情况下,你可以监听事件并主动尝试重连,但现实中的大多数情况都是“意料之外”的。那么如何有效感知各种异常情况呢?心跳检测和大多数检测场景一样,最有效的检测方式就是心跳检测。客户端可以通过定时发送心跳包来感知连接中断。从时效性的角度来看,心跳间隔越短越好,频繁的心跳检测必然会导致用户流量流失和耗电,所以我们的目标是如何第一时间检测到长时间连接中断的突发情况尽可能少的心跳检测。状态机+消息心跳队列:在心跳协议的设计中,需要注意的是,心跳包的核心目标是检测长期连接通道是否畅通。因此,上行报文和返回包的数据包要尽量小。一般来说,可以通过协议头和响应来识别心跳策略。心跳策略是实现我们上述目标的核心机制,但是心跳策略的详细设计甚至可以单独写一篇文章。本文仅简单罗列几种心跳策略。感兴趣的同学可以阅读文末的推荐文章继续深入研究。在短心跳检测初始状态下,连续3次ping收到ack后,可以认为处于稳定状态。常规定时心跳(根据不同app状态,频率可调节为Mid+、Mid-、Long)自适应心跳,自动适应设备网络状态变化心跳间隔冗余心跳,从app后台切换到后台前台,主动心跳消息ackandresend为了解决上述问题,引入了消息ackandresend机制。终端返回ack,服务器在accs消息下行时将消息加入重试队列。收到ack后更新消息到达状态,终止重试。总体设计流程图:本方案的难点在于重试处理器的实现设计。下面重点介绍这部分的详细设计。重试队列存储设计。我们使用阿里云表存储TimeLine模型来存储下行消息的到达状态。阿里云表格存储是阿里云自主研发的多模型结构化数据存储,提供海量结构化数据存储和快速查询分析服务。表格存储的分布式存储和强大的索引引擎可以支持PB级存储、千万级TPS、毫秒级时延的服务能力。Timeline模型是为消息数据场景设计的数据模型。可满足消息数据场景对消息保序、海量消息存储、实时同步的特殊需求。广泛应用于IM、feed流等消息场景。我们为每个用户设备定义一个TimeLine,timeline-id定义为userId_deviceId,sequenceId自定义为消息位置,存储结构如下:每一条消息通过accs下行成功,插入到TimeLine中接收用户设备,收到ack后,根据消息id更新消息到达状态。同时,由于重试动作只发生在下行消息后的短时间内,我们可以设置一个比较短的全局过期时间,避免数据膨胀。延迟重试设计每次通过accs发送消息,先插入到Timeline中,初始状态为unreached,然后产生延迟N秒的延迟消息。每条延迟消息被消费后,读取tablestore中消息的到达状态,如果到达则停止延时,否则继续每次重试判断设备是否在线,如果设备不在线则转发离线通道并终止重试,如果设备在线则重新推送消息未到达,再次延迟消费N秒每条消息的重试生命周期中使用的同一条延迟消息,最多可以重试消费M次。如果超过重试次数,则不再重试,日志会被埋掉。您可以监控这种情况并根据此数据优化延迟重发。Strategy延迟重传策略是指在重传过程中如何选择合适的延迟时间,使重传效率最大化。不同用户在不同时间、不同地点的网络环境差异很大,网络恢复稳定所需的时间也不同。需要选择合适的延迟策略来保证重传效率。最优延迟策略的目标是在最短的时间内,使用最少的重传次数成功传递消息。如果要找到延迟时间固定的最优延迟策略,就必须通过分析从数据中得到答案。天马行空的想象往往与现实相去甚远。我们先用固定的延迟时间(10s),最多重试6次来分析一波我们从这组数据可以看出,大约85%的消息在40s内重发后可以投递成功,12%的消息达到最大重试次数后,消息仍然没有收到确认。重试4次后,第五次成功率只有2.03%,第六次成功率只有0.92%。继续重发的收益已经变得很低了。第6次之后,有些消息还没有收到ack。如果对这些消息采用固定延时策略,性价比很低,频繁重传浪费系统资源,我们继续改进该策略。固定延时+固定步长增量考虑到部分用户的网络无法在短时间内恢复,频繁的短间隔重传价值不大。我们使用4个固定的短间隔延迟N秒,之后的每个延迟时间都是最后一个延迟时间以固定步长M秒的策略递增,直到收到ack,用户设备离线,或者达到最大延迟时间MAX(N)。这种策略在一定程度上可以解决固定延迟时间重传策略的问题,但是如果用户短时间内无法恢复网络,那么每次重传都必须重新增加增量,这不是一个最优解。自适应延迟设计流程图:如上图所示,我们最终推导出了一种自适应延迟策略。自适应延迟是指根据用户的网络情况自动调整延迟时间,以达到最高的重传效率。首先发送新消息。通过4个固定的N秒短延时检测设备的网络状态。一旦网络恢复,我们将清除设备的N值。设备的N值是指当前设备网络可以根据之前的重传经验回复ack。时间最短。默认为空,表示用户设备网络正常。经过4次重传,还是没有收到ack。我们尝试读取设备的N值。如果为空,取初始值。每次延迟后,固定步长M会递增,重传后更新当前设备的N值,直到消息收到ack或达到最大延迟时间MAX(N)。新旧版本兼容需要注意的是,旧版本的app是不会返回ack的。如果发送给旧版本设备的消息也被加入到重试队列中,那么这样的消息会被重试到最大次数后才被终止。无端消耗资源,所以我们设计accs长连接建立后,客户端主动上传一条设备信息,其中包含app的版本号,服务端保存一定时间时间。在将消息添加到重试队列之前,它首先检查接收方设备应用程序的版本号,是否符合要求,然后加入重试队列。方案效果消息重连重发方案上线后,我们上面定义的指标的accs补偿到达时间从60分钟大幅降低到15分钟,降幅达75%,印证了我们的技术分析。同时,用户对消息延迟的舆情反馈每周不超过2条,可见消息重发机制有效保证了用户消息的及时到达。未来展望新闻在线频道稳定性优化告一段落。未来我们会持续优化闲鱼新闻的使用体验,包括基础功能的完善和基础体验的提升。在基础功能方面,我们在最近的版本中已经支持消息召回和草稿功能,将逐步支持发送定位、会话分组、便签、消息搜索等功能。优化升级,优化了CPU和内存占用APP消息标签页功能,将在流量、电量、性能等方面持续优化消息体验。
