本文由阿里闲鱼技术团队景松分享,原题“达到率 99.9%:闲鱼音讯在高速上换引擎(集大成)”,有订正和改变,感激作者的分享。
1、引言
在 2020 年年初的时候接手了闲鱼的 IM 即时消息零碎,过后的音讯存在各种问题,网上的用户舆情也是接连不断。
典型的问题,比方:
1)“聊天音讯常常失落”;
2)“音讯用户头像乱了”;
3)“订单状态不对”(置信当初看文章的你还在吐槽闲鱼的音讯)。
所以闲鱼的即时消息零碎稳定性、可靠性是一个亟待解决的问题。
咱们调研了团体内的一些解决方案,例如钉钉的 IMPass。如果贸然间接迁徙,技术老本和危险都是比拟大,包含服务端数据须要双写、新老版本兼容等。
那么基于闲鱼现有的即时消息零碎架构和技术体系,如何来优化它的音讯稳定性、可靠性?应该从哪里开始治理?以后零碎现状到底是什么样?如何主观进行掂量?心愿本文能让大家看到一个不一样的闲鱼即时消息零碎。
PS:如果您对 IM 音讯可靠性还没有概念,倡议先浏览这篇入门文章《零根底 IM 开发入门(三):什么是 IM 零碎的可靠性?》。
学习交换:
- 即时通讯 / 推送技术开发交换 5 群:215477170 [举荐]
- 挪动端 IM 开发入门文章:《新手入门一篇就够:从零开发挪动端 IM》
- 开源 IM 框架源码:https://github.com/JackJiang2…
(本文同步公布于:http://www.52im.net/thread-37…)
2、系列文章
本文是系列文章的第 4 篇,总目录如下:
《阿里 IM 技术分享(一):企业级 IM 王者——钉钉在后端架构上的过人之处》
《阿里 IM 技术分享(二):闲鱼 IM 基于 Flutter 的挪动端跨端革新实际》
《阿里 IM 技术分享(三):闲鱼亿级 IM 音讯零碎的架构演进之路》
《阿里 IM 技术分享(四):闲鱼亿级 IM 音讯零碎的牢靠投递优化实际》(* 本文)
3、行业计划
通过查阅网上分享的支流音讯牢靠投递技术计划,我进行了简略总结。
通常 IM 音讯的投递链路大抵分为三步:
1)发送者发送;
2)服务端接管而后落库;
3)服务端告诉接收端。
特地是挪动端的网络环境比较复杂:
1)可能你发着音讯,网络忽然断掉了;
2)可能音讯正在发送中,网络忽然好了,须要重发。
技术原理图大如下:
PS:可能很多人对挪动网络的复杂性没有个零碎的认知,以下文章有必要零碎浏览:
《通俗易懂,了解挪动网络的“弱”和“慢”》
《史上最全挪动弱网络优化办法总结》
《为什么 WiFi 信号差?一文即懂!》
《为什么手机信号差?一文即懂!》
《高铁上无线上网有多难?一文即懂!》
那么,在如此简单的网络环境下,是如何稳固牢靠的进行 IM 音讯投递的?
对于发送者来说,它不晓得音讯是否有送达,要想做到确定送达,就须要加一个响应机制。
这个机制相似于上面的响应逻辑:
1)发送者发送了一条音讯“Hello”,进入期待状态;
2)接收者收到这条音讯“Hello”,而后通知发送者说我曾经收到这条音讯了的确认信息;
3)发送者接管到确认信息后,这个流程就算实现了,否则会重试。
下面流程看似简略,要害是两头有个服务端转发过程,问题就在于谁来回这个确认信息,以及什么时候回这个确认信息。
网上查到比拟多的是如下一个音讯必达模型:
各报文类型解释如下:
如下面两图所示,发送流程是:
1)A 向 IM-server 发送一个音讯申请包,即 msg:R1;
2)IM-server 在胜利解决后,回复 A 一个音讯响应包,即 msg:A1;
3)如果此时 B 在线,则 IM-server 被动向 B 发送一个音讯告诉包,即 msg:N1(当然,如果 B 不在线,则音讯会存储离线)。
如下面两图所示,接管流程是:
1)B 向 IM-server 发送一个 ack 申请包,即 ack:R2;
2)IM-server 在胜利解决后,回复 B 一个 ack 响应包,即 ack:A2;
3)IM-server 被动向 A 发送一个 ack 告诉包,即 ack:N2。
正如上述模型所示:一个牢靠的音讯投递机制就是靠的 6 条报文来保障的,两头任何一个环节出错,都能够基于这个 request-ack 机制来断定是否出错并重试。
咱们最终采纳的计划也正是参考了下面这个模型,客户端发送的逻辑是间接基于 http 的所以临时不必做重试,次要是在服务端往客户端推送的时候,会加上重试的逻辑。
限于篇幅,本文就不具体开展,有趣味能够零碎学习以下几篇:
《从客户端的角度来谈谈挪动端 IM 的音讯可靠性和送达机制》
《IM 音讯送达保障机制实现(一):保障在线实时音讯的牢靠投递》
《IM 音讯送达保障机制实现(二):保障离线音讯的牢靠投递》
《齐全自已开发的 IM 该如何设计“失败重试”机制?》
《一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等》
《了解 IM 音讯“可靠性”和“一致性”问题,以及解决方案探讨》
《融云技术分享:全面揭秘亿级 IM 音讯的牢靠投递机制》
4、以后面临的具体问题
4.1 概述
在解决音讯牢靠投递这个问题之前,咱们必定首先应该搞清楚以后面临的具体问题到底有哪些。
然而在接手这套即时消息零碎时,并没有相干的精确数据可供参考,所以以后第一步还是要对这套音讯零碎做一个残缺的排查,于是咱们对音讯做了全链路埋点。
具体的埋点环节如下:
基于音讯的整个链路,咱们梳理进去了几个要害的指标:
1)发送成功率;
2)音讯达到率;
3)客户端落库率。
这次整个数据的统计都是基于埋点来做的,但在埋点的过程中发现了一个很大的问题:以后这套即时消息零碎没有一个全局惟一的音讯 ID。这导致在全链路埋点的过程中,无奈惟一确定这条音讯的生命周期。
4.2 音讯唯一性问题
如上图所示,以后的音讯是通过 3 个变量来确定唯一性的:
1)SessionID: 以后会话的 ID;
2)SeqID:用户以后本地发送的音讯序号,服务端是不关怀此数据,齐全是透传;
3)Version:这个比拟重要,是音讯在以后会话中的序号,已服务端为准,然而客户端也会生成一个假的 version。
以上图为例:当 A 和 B 同时发送音讯的时候,都会在本地生成如上几个要害信息,当 A 发送的音讯(黄色)首先达到服务端,因为后面没有其余 version 的音讯,所以会将原数据返回给 A,客户端 A 接管到音讯的时候,再跟本地的音讯做合并,只会保留一条音讯。同时服务端也会将此音讯发送给 B,因为 B 本地也有一个 version= 1 的音讯,所以服务端过去的音讯就会被过滤掉,这就呈现音讯失落的问题。
当 B 发送音讯达到服务端后,因为曾经有 version= 1 的音讯,所以服务端会将 B 的音讯 version 递增,此时音讯的 version=2。这条音讯发送给 A,和本地音讯能够失常合并。然而当此音讯返回给 B 的时候,和本地的音讯合并,会呈现 2 条一样的音讯,呈现音讯反复,这也是为什么闲鱼之前总是呈现音讯失落和音讯反复最次要的起因。
4.3 音讯推送逻辑问题
以后音讯的推送逻辑也存在很大的问题,发送端因为应用 http 申请,发送的音讯内容根本不会出问题,问题是呈现在服务端给另外一端推送的时候。
如下图所示:
如上图所示:服务端在给客户端推送的时候,会先判断此时客户端是否在线,如果在线才会推送,如果不在线就会推离线音讯。
这个做法就十分的简略粗犷:长连贯的状态如果不稳固,导致客户端实在状态和服务端的存储状态不统一,就导致音讯不会推送到端上。
4.4 客户端逻辑问题
除了以上跟服务端有关系外,还有一类问题是客户端自身设计的问题。
能够归结为以下几种状况:
1)多线程问题:反馈音讯列表页面会呈现布局错乱,本地数据还没有齐全初始化好,就开始渲染界面;
2)未读数和小红点的计数不准:本地的显示数据和数据库存储的不统一;
3)音讯合并问题:本地在合并音讯的时候,是分段合并的,不能保障音讯的连续性和唯一性。
诸如以上的几种状况,咱们首先是对客户端的代码做了梳理与重构。
架构如下图所示:
5、咱们的优化工作 1:降级通心外围
解决问题第一步就是解决以后音讯零碎唯一性的问题。
咱们也调研了钉钉的计划,钉钉是服务端全局保护音讯的惟一 ID,思考到闲鱼即时消息零碎的历史包袱,咱们这边采纳 UUID 作为音讯的惟一 ID,这样就能够在音讯链路埋点以及去重上失去很大的改善。
5.1 解决音讯唯一性
在新版本的 APP 下面,客户端会生成一个 uuid,对于老版本无奈生成的状况,服务端也会补充上相干信息。
音讯的 ID 相似于 a1a3ffa118834033ac7a8b8353b7c6d9,客户端在接管到音讯后,会先依据 MessageID 来去重,而后基于 Timestamp 排序就能够了,尽管客户端的工夫可能不一样,然而反复的概率还是比拟小。
以 iOS 端为例,代码大抵如下:
(void)combileMessages:(NSArray<PMessage>)messages {
…
// 1. 依据音讯 MessageId 进行去重
NSMutableDictionary *messageMaps = [self containerMessageMap];
for (PMessage *message in msgs) {[messageMaps setObject:message forKey:message.messageId];
}
援用
// 2. 音讯合并后排序
NSMutableArray *tempMsgs = [NSMutableArray array];
[tempMsgs addObjectsFromArray:messageMaps.allValues];
[tempMsgs sortUsingComparator:^NSComparisonResult(PMessage _Nonnull obj1, PMessage _Nonnull obj2) {// 依据音讯的 timestamp 进行排序 return obj1.timestamp > obj2.timestamp;
}];
…
}
5.2 实现音讯重发、断线重连机制
基于本文“3、行业计划”一节中的重发重连模型,咱们欠缺了服务端的音讯重发的逻辑、客户端欠缺了断线重连的逻辑。
具体措施是:
1)客户端会定时检测 ACCS 长连贯是否联通;
2)服务端会检测设施是否在线,如果在线会推送音讯,并会有超时期待;
3)客户端接管到音讯之后,会返回一个 Ack。
5.3 优化数据同步逻辑
重发重连解决的根底网络层的问题,接下来就要看下业务层的问题。
现有音讯零碎中,很多简单状况是通过在业务层减少兼容代码来解决的,音讯的数据同步就是一个很典型的场景。
在欠缺数据同步的逻辑之前,咱们也调研过钉钉的一整套数据同步计划,他们次要是由服务端来保障的,背地有一个稳固的长连贯保障。
钉钉的数据同步计划大抵流程如下:
咱们的服务端临时还没有这种能力,所以闲鱼这边只能从客户端来控制数据同步的逻辑。
数据同步的形式包含:
1)拉取会话;
2)拉取音讯;
3)推送音讯等。
因为波及到的场景比较复杂,之前有个场景就是推送会触发增量同步,如果推送过多的话,会同时触发屡次网络申请,为了解决这个问题,咱们也做了相干的推拉队列隔离。
客户端管制的策略就是如果在拉取的话,会先将 push 过去的音讯加到缓存队列外面,等拉取的后果回来,会再跟本地缓存的逻辑做合并,这样就能够防止屡次网络申请的问题。
5.4 客户端数据模型优化
客户端在数据组织模式上,次要分 2 中:会话和音讯,会话又分为:虚构节点、会话节点和文件夹节点。
在客户端会构建上图一样的树,这棵树次要保留的是会话显示的相干信息,比方未读数、红点以及最新消息摘要,子节点更新,会顺带更新到父节点,构建树的过程也是已读和未读数更新的过程。
其中比较复杂的场景是闲鱼情报社,这个其实是一个文件夹节点,它蕴含了很多个子的会话,这就决定了他的音讯排序、红点计数以及音讯摘要的更新逻辑会更简单,服务端告知客户端子会话的列表,而后客户端再去拼接这些数据模型。
5.5 服务端存储模型优化
在前述内容中,我大略讲了客户端的申请逻辑,即历史音讯会分为增量和全量域同步。
这个域其实是服务端的一层概念,实质上就是用户音讯的一层缓存,音讯过去之后会暂存在缓存中,减速音讯读取。
然而这个设计也存在一个缺点:就是域环是有长度的,最多保留 256 条,当用户的音讯数多于 256 条,只能从数据库中读取。
对于服务端的存储形式,咱们也调研过钉钉的计划——是写扩散,长处就是能够很好地对每位用户的音讯做定制化,毛病就是存储量很很大。
咱们的这套解决方案,应该是介于读扩散和写扩散之间的一种解决方案。这个设计形式不仅使客户端逻辑简单,服务端的数据读取速度也会比较慢,后续这块也能够做优化。
6、咱们的优化工作 2:减少品质监控体系
在做客户端和服务端的全链路革新的同时,咱们也对音讯线上的行为做了监控和排查的逻辑。
6.1 全链路排查
全链路排查是基于用户的实时行为日志,客户端的埋点通过团体实时处理引擎 Flink,将数据荡涤到 SLS 外面。
用户的行为包含:
1)音讯引擎对音讯的解决;
2)用户的点击 / 拜访页面的行为;
3)用户的网络申请。
服务端侧会有一些长连贯推送以及重试的日志,也会荡涤到 SLS,这样就组成了从服务端到客户端全链路的排查的计划。
6.2 对账零碎
当然为了验证音讯的准确性,咱们还做了对账零碎:
在用户来到会话的时候,咱们会统计以后会话肯定数量的音讯,生成一个 md5 的校验码,上报到服务端。服务端拿到这个校验码之后再断定是否音讯是正确的。
通过抽样数据验证,音讯的准确性根本都在 99.99%。
7、数据指标统计办法优化
咱们在统计音讯的要害指标时,遇到点问题:之前咱们是用用户埋点来统计的,发现会有 3%~5% 的数据差。
起初咱们采纳抽样实时上报的数据来计算数据指标:
音讯达到率 = 客户端理论收到的音讯量 / 客户端应该收到的音讯量
客户端理论收到的音讯的定义为“音讯落库才算是”。
该指标不辨别离线在线,取用户当日最初一次更新设施工夫,实践上当天且在此工夫之前下发的音讯都应该收到。
通过前述优化工作,咱们最新版本的音讯达到率曾经根本达到 99.9%,从舆情上来看,反馈丢音讯的也的确少了很多。
8、将来布局
整体看来,通过一年的优化治理,咱们的即时消息零碎各项指标在缓缓变好。
但还是存在一些待优化的方面:
1)音讯的安全性有余:容易被黑产利用,借助音讯发送一些违规的内容;
2)音讯的扩展性较弱:减少一些卡片或者能力就要发版,短少了动态化和扩大的能力。
3)底层的伸缩性有余:当初底层协定比拟难扩大,后续还是要标准一下协定。
从业务角度看,音讯应该是一个横向撑持的工具性或者平台型的产品,且能够疾速对接二方和三方的疾速对接。
接下来,咱们会继续关注音讯相干的用户舆情,心愿闲鱼即时消息零碎能帮忙用户更好的实现业务交易。
附录:更多相干文章
[1] 更多阿里巴巴的技术资源:
《阿里钉钉技术分享:企业级 IM 王者——钉钉在后端架构上的过人之处》
《古代 IM 零碎中聊天音讯的同步和存储计划探讨》
《阿里技术分享:深度揭秘阿里数据库技术计划的 10 年变迁史》
《阿里技术分享:阿里自研金融级数据库 OceanBase 的艰苦成长之路》
《来自阿里 OpenIM:打造安全可靠即时通讯服务的技术实际分享》
《钉钉——基于 IM 技术的新一代企业 OA 平台的技术挑战 (视频 +PPT) [附件下载]》
《阿里技术结晶:《阿里巴巴 Java 开发手册(规约)- 华山版》[附件下载]》
《重磅公布:《阿里巴巴 Android 开发手册(规约)》[附件下载]》
《作者谈《阿里巴巴 Java 开发手册(规约)》背地的故事》
《《阿里巴巴 Android 开发手册(规约)》背地的故事》
《干了这碗鸡汤:从理发店小弟到阿里 P10 技术大牛》
《揭秘阿里、腾讯、华为、百度的职级和薪酬体系》
《淘宝技术分享:手淘亿级挪动端接入层网关的技术演进之路》
《难得干货,揭秘支付宝的 2 维码扫码技术优化实际之路》
《淘宝直播技术干货:高清、低延时的实时视频直播技术解密》
《阿里技术分享:电商 IM 音讯平台,在群聊、直播场景下的技术实际》
《阿里技术分享:闲鱼 IM 基于 Flutter 的挪动端跨端革新实际》
《阿里 IM 技术分享(三):闲鱼亿级 IM 音讯零碎的架构演进之路》
《阿里 IM 技术分享(四):闲鱼亿级 IM 音讯零碎的牢靠投递优化实际》
[2] 无关 IM 架构设计的文章:
《浅谈 IM 零碎的架构设计》
《简述挪动端 IM 开发的那些坑:架构设计、通信协议和客户端》
《一套海量在线用户的挪动端 IM 架构设计实际分享(含具体图文)》
《一套原创分布式即时通讯(IM) 零碎实践架构计划》
《从零到卓越:京东客服即时通讯零碎的技术架构演进历程》
《蘑菇街即时通讯 /IM 服务器开发之架构抉择》
《腾讯 QQ1.4 亿在线用户的技术挑战和架构演进之路 PPT》
《微信后盾基于工夫序的海量数据冷热分级架构设计实际》
《微信技术总监谈架构:微信之道——大道至简(演讲全文)》
《如何解读《微信技术总监谈架构:微信之道——大道至简》》
《疾速裂变:见证微信弱小后盾架构从 0 到 1 的演进历程(一)》
《挪动端 IM 中大规模群音讯的推送如何保障效率、实时性?》
《古代 IM 零碎中聊天音讯的同步和存储计划探讨》
《微信朋友圈千亿访问量背地的技术挑战和实际总结》
《子弹短信光鲜的背地:网易云信首席架构师分享亿级 IM 平台的技术实际》
《微信技术分享:微信的海量 IM 聊天音讯序列号生成实际(算法原理篇)》
《一套高可用、易伸缩、高并发的 IM 群聊、单聊架构方案设计实际》
《社交软件红包技术解密(一):全面解密 QQ 红包技术计划——架构、技术实现等》
《从游击队到正规军(一):马蜂窝旅游网的 IM 零碎架构演进之路》
《从游击队到正规军(二):马蜂窝旅游网的 IM 客户端架构演进和实际总结》
《从游击队到正规军(三):基于 Go 的马蜂窝旅游网分布式 IM 零碎技术实际》
《瓜子 IM 智能客服零碎的数据架构设计(整顿自现场演讲,有配套 PPT)》
《IM 开发基础知识补课(九):想开发 IM 集群?先搞懂什么是 RPC!》
《阿里技术分享:电商 IM 音讯平台,在群聊、直播场景下的技术实际》
《一套亿级用户的 IM 架构技术干货(上篇):整体架构、服务拆分等》
《一套亿级用户的 IM 架构技术干货(下篇):可靠性、有序性、弱网优化等》
《从老手到专家:如何设计一套亿级音讯量的分布式 IM 零碎》
《企业微信的 IM 架构设计揭秘:音讯模型、万人群、已读回执、音讯撤回等》
《融云技术分享:全面揭秘亿级 IM 音讯的牢靠投递机制》
《IM 开发技术学习:揭秘微信朋友圈这种信息推流背地的零碎设计》
《阿里 IM 技术分享(三):闲鱼亿级 IM 音讯零碎的架构演进之路》
本文已同步公布于“即时通讯技术圈”公众号。
同步公布链接是:http://www.52im.net/thread-37…