FinOps有望降低企业50%+的云成本! 了解详情
写点什么

向消息延迟说 bybye:闲鱼消息及时到达方案(详细)

  • 2021-01-23
  • 本文字数:4137 字

    阅读完需:约 14 分钟

向消息延迟说bybye:闲鱼消息及时到达方案(详细)

背景

IM 消息作为闲鱼用户重要的交易咨询工具,核心目标有两点,第一是保证用户的消息不丢失,第二是保证用户的消息及时送达接收方。IM 消息根据消息的接收方设备是否在线,分为离线和在线推送,数据显示目前闲鱼每天有超过一半以上的 IM 消息是走在线通道的,而在线消息的到达率、及时性是直接影响用户体验的,本文将着重分析优化在线通道的稳定性,保证用户消息及时到达。

面临哪些问题

端内长连接中断

在 IM 场景中,用户与云端通信频繁,且为了实现用户的消息及时到达,往往采用云端下推消息的方式触达用户,所以用户在线时设备与云端会维持一条 TCP 长连接通道,可以更轻量级的与服务端进行交互,现代 IM 即时通讯的下行消息都是通过长连下发的,闲鱼消息使用的是 ACCS 长连接,ACCS 是淘宝无线提供的全双工、低延时、高安全的通道服务。但是由于用户设备网络状态的不确定性,可能会发生各种各样的网络异常情况导致长连接通道中断,长连接一旦意外中断,就会导致用户无法及时收到在线消息,所以我们需要尽可能及时的感知到长连中断并尝试重连。


null

下推的消息未达

感知长连中断并重连只能在大多数时间保证长连接的有效性,但是在长连接无效或不稳定期间下推的消息客户端可能根本收不到,简单说就是仅仅有重连机制无法保证下行消息必达,可能有以下场景导致下行消息失败:

  • 服务端发送下行消息时长连畅通,消息在传输路上通道断掉,客户端无法收到 

  • 设备的在线状态存在延迟,服务端下行消息时认为设备在线,实际上设备已经离线,无法收到 

  • 客户端收到了下行消息,但端上后续处理失败,比如落库失败,消息没有成功展示给用户

我们通过数据埋点统计得出,accs 下行成功率在 97%左右


有心急的同学就要问了,丢了 3%的消息吗?并没有,这 3%的消息不会丢失,只是不保证及时触达给用户。我们的消息同步模型是推拉结合模式,在用户拉取消息时会拉取到设备当前位点与服务端最新位点的所有消息,accs 下行失败的消息会通过主动拉模式获取到,但客户端主动拉取消息的触发时机有限,主要有以下几个:


  • 用户冷启动 app,主动同步消息 

  • 用户主动下拉刷新 

  • app 后台切换前台 

  • 收到一条推送消息,客户端发现新消息的位点跟本地最新的位点有 gap,触发同步


可见上述主动同步消息的触发很大程度上依赖用户行为或者有没有收到新消息,难以保证消息及时到达。如果是用户高频打开的 IM 软件,这样也不会有太大的问题,但是闲鱼 app 的活跃度较低,有时候甚至依赖 IM 消息拉活,而且一条延迟的消息触达可能导致用户错过一笔交易,闲鱼消息不允许有这样的延迟发生。基于上述分析,我们先描述一个数据指标来反映现状,通过上面的描述可知,accs 消息并不全都是推下来的,也可能是主动拉下来的,如果是推,必定可以及时到达,如果是拉,则受限于用户行为。拉的这部分消息,我们定义为 accs 消息补偿到达,然后计算 accs 消息补偿到达耗时,消息范围限定为服务端 accs 成功下行但是客户端通过主动拉取同步到的消息,以往的版本这个数据在 60 分钟左右。需要注意这个数据并不是消息触达到用户的耗时,因为如果在线转离线触达,拉取到消息的时间取决于用户行为(用户何时打开了 app),但这个数据也能大致反映在线消息的到达延迟状况。

接下来本文将从长连接的重连和未达消息重发两个方面详细讲述我们是如何优化在线通道稳定性的。

长连接重连

长连接为什么会中断?

百因必有果,我们先来分析下有哪些原因会导致连接中断,可能有以下原因: 


  • 用户设备断网 

  • 设备发生了网络切换 

  • 设备处于弱网环境,网络不稳定 

  • 设备网络正常,TCP 连接由于 NAT 超时导致连接被运营商中断


如果是用户操作导致网络状态变化的情况,会有网络状态变化事件通知,这种情况可以监听事件并主动尝试重连,但现实中的大多数情况都是“意料之外”。那么如何有效感知到各种异常状况呢?

心跳检测

像大多数探活场景一样,最有效的检测手段就是心跳检测,客户端通过定时发送心跳包,可以感知到连接中断,从及时性效果来看,心跳间隔越短越好,而频繁的心跳检测势必会带来用户流量以及电量的损耗,所以我们的目标是如何尽可能少的心跳检测而又尽量及时地感知到长连中断的意外情况。

状态机+消息心跳队列:


null

在心跳协议设计上,要注意心跳包的核心目标是检测长连通道是否畅通,客户端主动上行心跳包且能收到服务端回包,就认为长连通道健康,所以上行消息以及回包的数据包应尽可能小,一般来说,通过协议头标识心跳包及响应即可

心跳策略

心跳策略是实现我们上述目标的核心机制,但关于心跳策略的详细设计甚至可以单独写一篇文章,本文仅简单列举几种心跳策略,有兴趣的同学可以阅读文末推荐的文章继续深入研究。


  • 短心跳检测 初始状态连续 ping 3 次 收到 ack 后,可以认为进入稳定状态

  • 常规固定时长心跳(根据 app 状态不同,频率可调 Mid+,Mid-, Long)

  • 自适应心跳 根据设备网络状态变化自动适应的心跳间隔

  • 冗余心跳,app 后台切前台,主动心跳一次

消息 ack 与重发

为了解决上面的问题,引入消息 ack 与重发机制,整体思路是客户端在收到 accs 消息并处理成功后,给服务端回一个 ack,服务端下行 accs 消息时将消息加入重试队列,收到 ack 后更新消息到达状态,并终止重试。


整体设计流程图:

null

该方案的难点即重试处理器的实现设计,接下来我们将重点讲述这部分的详细设计

重试队列存储设计

我们采用阿里云表格存储 TimeLine 模型来存储下行消息的到达状态,阿里云表格存储是阿里云自研的多模型结构化数据存储,提供海量结构化数据存储以及快速的查询和分析服务。表格存储的分布式存储和强大的索引引擎能够支持 PB 级存储、千万 TPS 以及毫秒级延迟的服务能力。而 Timeline 模型是针对消息数据场景所设计的数据模型,它能满足消息数据场景对消息保序、海量消息存储、实时同步的特殊需求,在 IM、Feed 流等消息场景应用广泛。


我们给每个用户设备定义一个 TimeLine,timeline-id 定义为 userId_deviceId,sequenceId 自定义为消息位点,存储结构如下: 

每通过 accs 成功下行一条消息,则插入到接收用户设备的 TimeLine 中,收到 ack 后根据消息 id 更新消息到达状态,同时由于重试动作只发生在下行消息后较短的一段时间内,所以我们设置一个比较短的全局过期时间即可,避免数据膨胀。

延迟重试设计


null


1)每通过 accs 下发一条消息,先插入到 Timeline 中,初始状态为未达,然后生产一条延迟 N 秒的延迟消息 

2)每次消费到延迟消息后,读取 tablestore 中该消息的到达状态,如到达则终止延迟,否则继续 

3)每次重试先判断设备是否在线,如果设备不在线,转发离线通道并终止重试,如果设备在线,则重推未到达的消息,并再次延迟 N 秒消费 

4)每条消息的重试生命周期中用的同一条延迟消息,最多重试消费 M 次,超过次数不再重试并打日志埋点,后续可以监控这种情况并基于这个数据进行优化

延迟重发策略

延迟重发策略是指在重发流程中,如何选择合适的延迟时间来使得重发的效率最高。不同用户在不同时间、地点所处的网络环境差别较大,网络恢复到稳定态所需要的时间也有差异,需要选用合适的延迟策略来保证重发效率,最优的延迟策略的目标是在最短的时间内,使用最少的重发次数将消息投递成功。

固定延迟时间

要想找到最优的延迟策略,必须从数据中通过分析得到答案,天马行空的想象往往离实际相差甚远,我们先采用固定的延迟时间(10s)最大重试 6 次来分析一波数据

我们通过这组数据可以看到,有约 85%的消息在 40s 内重发可以投递成功,还有 12%的消息在达到最大重试次数后依旧没有收到 ack,在 4 次重试之后,第 5 次成功只有 2.03%,第 6 次只有 0.92%,继续重发的收益已经变得很低,6 次以后还有部分消息没有收到 ack,这部分消息如果用固定延迟时间策略,性价比很低,频繁重发浪费系统资源,我们继续改进策略。

固定延迟+固定步长递增

考虑到部分用户的网络短时间无法恢复,频繁的短间隔重发价值不大,我们采用 4 次固定短间隔延迟 N 秒后,之后每次延迟时间都是上一次延迟时间递增固定步长 M 秒的策略,直到收到 ack、用户设备离线或者达到了最大延迟时间 MAX(N)。这种策略一定程度上可以解决固定延迟时间重发策略的问题,但如果用户短时间网络无法恢复,每次重发都要重新递增,也不是一种最优解。

自适应延迟

设计流程图:

null

如上图,我们最终衍生出了自适应延迟策略,自适应延迟是指根据用户的网络状况,采取自动调整的延迟时间,以期望达到最高的重发效率,新消息先通过 4 次固定 N 秒的短延迟来探测设备的网络状况,一旦网络恢复,我们将设备的 N 值清空,设备 N 值是指根据上几次重发经验,当前设备网络能回复 ack 所需要的最短时间,默认情况该值为空,代表用户设备网络正常。4 次重发后依旧收不到 ack,我们尝试读取设备 N 值,如果为空,则取初始值,以后每次延迟都递增固定步长 M,并在重发后更新当前设备的 N 值,直到消息收到 ack 或者达到了最大延迟时间 MAX(N)。

新老版本兼容性

需要注意的是老版本的 app 是不会回 ack 的,如果下发给老版本设备的消息也加入重试队列,那此类消息将一直重试到最大次数才会终止,无端消耗资源,所以我们设计在 accs 长连建立之后,客户端主动上行一条设备信息,其中包含 app 的版本号,服务端存储一定时间,在将消息加入重试队列之前,先校验接收者设备 app 的版本号,符合要求再加入重试队列。

方案效果

消息重连重发方案上线后,我们上面定义的指标 accs 补偿到达时间从 60 分钟大幅降低至 15 分钟,降幅达 75%,从而印证了我们的技术分析,同时用户有关消息延迟的舆情反馈每周不超过 2 个,可见消息重发机制对保证用户消息及时到达成效显著。

未来展望

消息在线通道稳定性优化至此已告一段落,未来我们将继续优化闲鱼消息的使用体验,包括基础功能的完善以及基础体验的提升。

  • 基础功能方面,我们在近期的版本中已经支持了消息撤回、草稿功能,后续将逐步支持发送定位,会话分组、备注,消息搜索等功能;

  • 基础体验方面,我们对闲鱼消息的 UI 样式做了优化升级,并优化了 app 消息 tab 页的 cpu 及内存使用,后续将继续从流量、电量、性能方面继续优化消息的使用体验。


References

[1]现代 IM 系统中的消息系统架构 - 架构篇: https://developer.aliyun.com/article/698301

[2]现代 IM 系统中的消息系统架构 - 模型篇: https://developer.aliyun.com/article/701593

[3]高并发 IM 系统架构优化实践: https://developer.aliyun.com/article/66461


本文转载自:闲鱼技术(ID:XYtech_Alibaba)

原文链接:向消息延迟说bybye:闲鱼消息及时到达方案(详细)

2021-01-23 21:501499

评论

发布
暂无评论
发现更多内容

模块二 分析微信朋友圈的高性能复杂度

ifc177

翻译:《实用的Python编程》09_02_Third_party

codists

Python

最新、最全、最详细的 MySQL 数据库学习笔记总结(2021最新版)

民工哥

MySQL 数据库 Linux 后端 运维工程师

联想小新潮7000安装CentOS后重装Windows10

笑春风

最详细的 K8S 学习笔记总结(2021最新版)!建议收藏

民工哥

Kubernetes 容器 运维 后端

架构实战营模块2作业

竹林七贤

架构实战营

架构实战营——作业二: 朋友圈架构分析

开拓纪

微信朋友圈 #架构实战营

数据仓库为什么要分层

五分钟学大数据

数据仓库 4月日更

【AI全栈SOTA综述 】这些你都不知道,怎么敢说会AI?【语音识别原理+实战】

cv君

AI 算法 音视频 引航计划

架构实战营 -- 模块二

永佳

架构实战营

写作平台一周年 | 我曾陪伴走过四季春秋

架构精进之路

个人总结 4月日更 1 周年盛典 我和写作平台的故事 InfoQ 写作平台 1 周年

听说你们写毕业设计没有动态数据?Python教你一步完成!

大数据老哥

面试总结-Java-2年

U2647

Java 面试 4月日更

架构实战营 - 模块 02 作业

架构实战营

工作中的设计模式 —— 建造者模式

程序员小航

Java 设计模式 建造者模式

Toolkit 大更新:UI 更美观,用起来更方便!

程序员小航

Java IDEA idea插件 IntelliJ IDEA JSON格式化

看看别人家 SpringBoot 的全局异常处理,多么优雅....

Java小咖秀

springboot 全局异常

架构实战训练营 - 模块二课后作业

Johnny

架构实战营

PHP异常处理

Sakura

4月日更

模块二作业

Chris Cheng

架构实战营

Oozie平台调度

大数据技术指南

oozie 4月日更

Prometheus counter 四大 query 函数详解

Grafana 爱好者

云原生 Prometheus 可观察性 PromQL

谈商业软件的发展趋势

张驰

volatile 关键字需要知道的几点

lich0079

Java volatile Disruptor unsafe false sharing

理解OT算法

张驰

爬虫IP代理池代码记录

空城机

Python 爬虫 代理IP 4月日更

架构师实战营-模块二作业

大可

python 异常处理

若尘

异常 异常处理 Python编程

架构训练营模块 2 作业 - 张动动

张大彪

架构训练营

智能小车系列-NODE版SBUS飞控协议解析历程

波叽波叽啵😮一口盐汽水喷死你

SBUS 飞控协议 SBUSReceiver S.BUS SBUSUART

以应用为中心的云原生2.0

8小时

  • 需要帮助,请添加网站小助手,进入 InfoQ 技术交流群
向消息延迟说bybye:闲鱼消息及时到达方案(详细)_文化 & 方法_闲鱼技术_InfoQ精选文章