共计 4941 个字符,预计需要花费 13 分钟才能阅读完成。
简介:记录这一年闲鱼音讯的优化之路
- 背景
======
在 2020 年年初的时候接手了闲鱼的音讯,过后的音讯存在各种问题,网上的舆情也是接连不断:“闲鱼音讯常常失落”、“音讯用户头像乱了”、“订单状态不对”(置信当初看文章的你还在吐槽闲鱼的音讯)。所以闲鱼的稳定性是一个亟待解决的问题,咱们调研了团体的一些解决方案,例如钉钉的 IMPass。间接迁徙的老本和危险都是比拟大,包含服务端数据须要双写、新老版本兼容等。
那基于闲鱼现有的音讯架构和体系,如何来保障它的稳定性?治理应该从哪里开始?当初闲鱼的稳定性是什么样的?如何掂量稳定性?心愿这篇文章,能让大家看到一个不一样的闲鱼音讯。
- 行业计划
========
音讯的投递链路大抵分为三步:发送者发送,服务端接管而后落库,服务端告诉接收端。特地是挪动端的网络环境比较复杂,可能你发着音讯,网络忽然断掉了;可能音讯正在发送中,网络忽然好了,须要重发。
在如此简单的网络环境下,是如何稳固牢靠的进行音讯投递的?对发送者来说,它不晓得音讯是否有送达,要想做到确定送达,就须要加一个响应机制,相似上面的响应逻辑:
- 发送者发送了一条音讯“Hello”,进入期待状态。
- 接收者收到这条音讯“Hello”,而后通知发送者说我曾经收到这条音讯了的确认信息。
- 发送者接管到确认信息后,这个流程就算实现了,否则会重试。
下面流程看似简略,要害是两头有个服务端转发过程,问题就在于谁来回这个确认信息,什么时候回这个确认信息。网上查到比拟多的是如下一个必达模型,如下图所示:
[发送流程]
- A 向 IM-server 发送一个音讯申请包,即 msg:R1
- IM-server 在胜利解决后,回复 A 一个音讯响应包,即 msg:A1
- 如果此时 B 在线,则 IM-server 被动向 B 发送一个音讯告诉包,即 msg:N1(当然,如果 B 不在线,则音讯会存储离线)
[接管流程]
- B 向 IM-server 发送一个 ack 申请包,即 ack:R2
- IM-server 在胜利解决后,回复 B 一个 ack 响应包,即 ack:A2
- 则 IM-server 被动向 A 发送一个 ack 告诉包,即 ack:N2
一个可信的音讯送达零碎就是靠的 6 条报文来保障的,有这个投递模型来决定音讯的必达,两头任何一个环节出错,都能够基于这个 request-ack 机制来断定是否出错并重试。看下在第 4.2 章中,也是参考了下面这个模型,客户端发送的逻辑是间接基于 http 的所以临时不必做重试,次要是在服务端往客户端推送的时候,会加上重试的逻辑。
- 闲鱼音讯的问题
===========
刚接手闲鱼音讯,没有稳固相干的数据,所以第一步还是要对闲鱼音讯做一个零碎的排查,首先对音讯做了全链路埋点。
基于音讯的整个链路,咱们梳理进去了几个要害的指标:发送成功率、音讯达到率、客户端落库率。整个数据的统计都是基于埋点来做的。在埋点的过程总,发现了一个很大的问题:闲鱼的音讯没有一个全局惟一的 ID,导致在全链路埋点的过程中,无奈惟一确定这条音讯的生命周期。
3.1 音讯唯一性问题
之前闲鱼的音讯是通过 3 个变量来惟一确定一个音讯
- SessionID: 以后会话的 ID
- SeqID:用户以后本地发送的音讯序号,服务端是不关怀此数据,齐全是透传
- Version:这个比拟重要,是音讯在以后会话中的序号,已服务端为准,然而客户端也会生成一个假的 version
以上图为例,当 A 和 B 同时发送音讯的时候,都会在本地生成如上几个要害信息,当 A 发送的音讯(黄色)首先达到服务端,因为后面没有其余 version 的音讯,所以会将原数据返回给 A,客户端 A 接管到音讯的时候,再跟本地的音讯做合并,只会保留一条音讯。同时服务端也会将此音讯发送给 B,因为 B 本地也有一个 version= 1 的音讯,所以服务端过去的音讯就会被过滤掉,这就呈现音讯失落的问题。
当 B 发送音讯达到服务端后,因为曾经有 version= 1 的音讯,所以服务端会将 B 的音讯 version 递增,此时音讯的 version=2。这条音讯发送给 A,和本地音讯能够失常合并。然而当此音讯返回给 B 的时候,和本地的音讯合并,会呈现 2 条一样的音讯,呈现音讯反复,这也是为什么闲鱼之前总是呈现音讯失落和音讯反复最次要的起因。
3.2 音讯推送逻辑问题
之前闲鱼的音讯的推送逻辑也存在很大的问题,发送端应用 http 申请,发送音讯内容,根本不会出问题,问题是呈现在服务端给另外一端推送的时候。如下图所示,
服务端在给客户端推送的时候,会先判断此时客户端是否在线,如果在线才会推送,如果不在线就会推离线音讯。这个做法就十分的简略粗犷。长连贯的状态如果不稳固,导致客户端实在状态和服务端的存储状态不统一,就导致音讯不会推送到端上。
3.3 客户端逻辑问题
除了以上跟服务端有关系外,还有一类问题是客户端自身设计的问题,能够归结为以下几种状况:
- 多线程问题
反馈音讯列表页面会呈现布局错乱,本地数据还没有齐全初始化好,就开始渲染界面
- 未读数和小红点的计数不精确
本地的显示数据和数据库存储的不统一。
- 音讯合并问题
本地在合并音讯的时候,是分段合并的,不能保障音讯的连续性和唯一性。
诸如以上的几种状况,咱们首先是对客户端的代码做了梳理与重构,架构如下图所示:
- 咱们的解法 – 引擎降级
================
进行治理的第一步就是,解决闲鱼音讯的唯一性的问题。咱们也调研了钉钉的计划,钉钉是服务端全局保护音讯的惟一 ID,思考到闲鱼音讯的历史包袱,咱们这边采纳 UUID 作为音讯的惟一 ID,这样就能够在音讯链路埋点以及去重上失去很大的改善。
4.1 音讯唯一性
在新版本的 APP 下面,客户端会生成一个 uuid,对于老版本无奈生成的状况,服务端也会补充上相干信息。
音讯的 ID 相似 a1a3ffa118834033ac7a8b8353b7c6d9,客户端在接管到音讯后,会先依据 MessageID 来去重,而后基于 Timestamp 排序就能够了,尽管客户端的工夫可能不一样,然而反复的概率还是比拟小。
- (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;
}];
...
}
4.2 重发重连
基于 #2 中的重发重连模型,闲鱼欠缺了服务端的重发的逻辑,客户端欠缺了重连的逻辑。
- 客户端会定时检测 ACCS 长连贯是否联通
- 服务端会检测设施是否在线,如果在线会推送音讯,并会有超时期待
- 客户端接管到音讯之后,会返回一个 Ack
曾经有小伙伴发表了一篇文章:《向音讯提早说 bybye:闲鱼音讯及时达到计划(具体)》,解说了下对于网络不稳固给闲鱼音讯带来的问题,在这里就不多赘述了。
4.3 数据同步
重发重连是解决的根底网络层的问题,接下来就要看下业务层的问题,很多简单状况是通过在业务层减少兼容代码来解决的,闲鱼音讯的数据同步就是一个很典型的场景。在欠缺数据同步的逻辑之前,咱们也调研过钉钉的一整套数据同步计划,他们次要是由服务端来保障的,背地有一个稳固的长连贯保障,大抵流程如下:
闲鱼的服务端临时还没有这种能力,起因详见 4.5 的服务端存储模型。所以闲鱼这边只能从客户端来控制数据同步的逻辑,数据同步的形式包含:拉取会话、拉取音讯、推送音讯等。因为波及到的场景比较复杂,之前有个场景就是推送会触发增量同步,如果推送过多的话,会同时触发屡次网络申请,为了解决这个问题,咱们也做了相干的推拉队列隔离。
客户端管制的策略就是如果在拉取的话,会先将 push 过去的音讯加到缓存队列外面,等拉取的后果回来,会再跟本地缓存的逻辑做合并,这样就能够防止屡次网络申请的问题。之前共事曾经写了一篇对于推拉流控制的逻辑,《如何无效缩短闲鱼音讯解决时长》,这里也不过多赘述了。
4.4 客户端模型
客户端在数据组织模式上,次要分 2 中:会话和音讯,会话又分为虚构节点、会话节点和文件夹节点。
在客户端会构建上图一样的树,这棵树次要保留的是会话显示的相干信息,比方未读数、红点以及最新消息摘要,子节点更新,会顺带更新到父节点,构建树的过程也是已读和未读数更新的过程。其中比较复杂的场景是闲鱼情报社,这个其实是一个文件夹节点,它蕴含了很多个子的会话,这就决定了他的音讯排序、红点计数以及音讯摘要的更新逻辑会更简单,服务端告知客户端子会话的列表,而后客户端再去拼接这些数据模型。
4.5 服务端存储模型
在 4.3 中大略讲了客户端的申请逻辑,历史音讯会分为增量和全量域同步。这个域其实是服务端的一层概念,实质上就是用户音讯的一层缓存,音讯过去之后会暂存在缓存中,减速音讯读取。然而这个设计也存在一个缺点,就是域环是有长度的,最多保留 256 条,当用户的音讯数多于 256 条,只能从数据库中读取。
对于服务端的存储形式,咱们也调研过钉钉的计划,是写扩散,长处就是能够很好地对每位用户的音讯做定制化,比方钉的逻辑,毛病就是存储量很很大。闲鱼的这套解决方案,应该是介于读扩散和写扩散之间的一种解决方案。这个设计形式不仅使客户端逻辑简单,服务端的数据读取速度也会比较慢,后续这块也能够做优化。
- 咱们的解法 – 品质监控
================
在做客户端和服务端的全链路革新的同时,咱们也对音讯线上的行为做了监控和排查的逻辑。
5.1 全链路排查
全链路排查是基于用户的实时行为日志,客户端的埋点通过团体实时处理引擎 Flink,将数据荡涤到 SLS 外面,用户的行为包含了音讯引擎对音讯的解决、用户的点击 / 拜访页面的行为、以及用户的网络申请。服务端测会有一些长连贯推送以及重试的日志,也会荡涤到 SLS,这样就组成了从服务端到客户端全链路的排查的计划,详情请参考《音讯品质平台系列文章 | 全链路排查篇》。
5.2 对账零碎
当然为了验证音讯的准确性,咱们还做了对账零碎。
在用户来到会话的时候,咱们会统计以后会话肯定数量的音讯,生成一个 md5 的校验码,上报到服务端。服务端拿到这个校验码之后再断定是否音讯是正确的,通过抽样数据验证,音讯的准确性根本都在 99.99%。
6 外围数据指标
咱们在统计音讯的要害指标的时候,遇到点问题,之前咱们是用用户埋点来统计的,发现会有 3%~5% 的数据差;所以起初咱们采纳抽样实时上报的数据来计算数据指标。
音讯达到率 = 客户端理论收到的音讯量 / 客户端应该收到的音讯量
客户端理论收到的音讯的定义为音讯落库才算是
该指标不辨别离线在线,取用户当日最初一次更新设施工夫,实践上当天且在此工夫之前下发的音讯都应该收到。
最新版本的达到率曾经根本达到 99.9%,从舆情上来看,反馈丢音讯的也的确少了很多。
7. 将来布局
========
整体看来,通过一年的治理,闲鱼的音讯在缓缓的变好,但还是存在一些待优化的方面:
- 当初音讯的安全性有余,容易被黑产利用,借助音讯发送一些违规的内容。
- 音讯的扩展性较弱,减少一些卡片或者能力就要发版,短少了动态化和扩大的能力。
- 当初底层协定比拟难扩大,后续还是要标准一下协定。
- 从业务角度看,音讯应该是一个横向撑持的工具性或者平台型的产品,布局能够疾速对接二方和三方的疾速对接。
在 2021 年,咱们会继续关注音讯相干的用户舆情,心愿闲鱼音讯能帮忙闲鱼用户更好的实现二手交易。
作者:闲鱼技术——景松
原文链接
本文为阿里云原创内容,未经容许不得转载