一、背景
图片加载作为重中之重的App体验指标,端侧的白屏问题则是其中最为严重的问题之一。想象一下如果你在浏览交易商品、社区帖子等核心场景下,图片无法完成加载是多么糟糕的体验。
网络作为图片资源加载的最主要来源途径,如果不能够快速的响应请求,那对上层图片库而言,就是巧妇难为无米之炊了。
而且,通过线上白屏问题归因,我们看到网络问题导致比例最高,占比达81.97%。除去常见的弱网/无网等问题外,还有很多各种各样的网络环境问题我们是可以进行优化的,例如设备不支持IPv6,CDN节点异常,证书超时等。
二、网络优化&监控概览
网络优化与监控体系
三、网络监控能力
网络异常导致的图片白屏问题,往往和当时的环境有关,例如用户连的WIFI不支持IPv6但是DNS返回的大部分是V6的IP等,此时仅靠几条带有SocketTimeoutException的网络日志根本无法排查出问题的根因,就如同ANR问题需要火焰图一样,白屏问题同样需要一套完善的监控体系来记录问题现场的信息,从而分析问题根因。
OkHttp基础监控
图片网络请求的阶段信息,可以通过实现OkHttp自带的EventListener来获取,在各个阶段开始和结束点记录下时间戳即可。
其中需要重点关注connectFailed,requestFailed和responseFailed这三个失败的回调,他们都会在失败之后重试,并重复执行阶段开始的回调(例如connectStart),因此针对这些需要单独记录好每一次失败信息,避免被覆盖。
例:某个图片请求TCP建连失败的记录
APM流量监控
尽管我们有专门的网络诊断工具,但是考虑到网络异常的滞后性,故障往往是数十秒之前引入的(例如进电梯弱网),而发生问题之后才触发的网络诊断结果仅能作为参考,并不能作为最终问题归因的证据。
因此我们需要一个更直观的表达用户设备网络状况的数据,那就是通过系统API获取的当前App流量消耗,在排除了一些本地socket通信产生的干扰之后,最近N秒之内消耗的流量/N就可以认为是白屏问题发生时的网速。
我们以3秒为间隔分段计算并取最大值来排除波动,这样我们就得到了白屏问题发生前一段时间内的网速状态,过低的就可以判定为弱网/无网。
网速计算示意图:
CDN异常记录
CDN厂商侧的异常,例如服务器IP跨省,节点挂了等问题相对少见,在网络日志中通常就表现为建连超时但是网络正常,因此需要收集多个Host的请求记录进行横向对比来确认是单个CDN厂商的问题。
为此我们记录了主站接口的域名,以及所有CDN域名在最近N分钟内的请求统计,包括了正常请求、慢请求、失败请求的数量和原因,以及失败请求使用的具体IP。
例:图中可以看到某CDN的请求全部都是建连失败,而其他CDN以及我们主站域名请求均正常,那么此问题可以归类为该CDN的单点问题,联系CDN服务商剔除该IP所在节点即可解决问题。
网络库请求缓存
常规的网络监控方案为了节省内存,会在请求结束时将其从内存缓存队列中移除,而白屏问题因为其滞后性,只有在屏幕中大面积出现白图才会被判定,此时再去查询加载失败的图片网络请求日志自然早已被移出队列,因此需要单独使用一个LRU队列来缓存最近N条网络日志,图片库同理。
网络诊断工具
在逐步解决用户问题的过程中,我们研发了网络诊断工具,实现了基础网络信息采集、Ping、TraceRoute等。
当检测到用户发生白屏时自动触发网络诊断。
单域名诊断流程
我们会依次进行DNS、Http、Ping(ICPM)与Ping(TCP)、TraceRoute诊断。如果是双栈客户端(同时返回了IPv4与IPv6),那么我们会选择首个IPv4与首个IPv6同时进行Ping/TraceRoute以确认不同IP类型的联通情况。
多域名诊断流程
同时对3个得物核心域名发起诊断。为了辅助判断,当得物核心域名诊断失败时,也会对一些主流三方域名稍微Ping一下。
诊断数据的上报
由于网络诊断可能耗时10秒以上,而如此长的时间内用户如果退出App那我们的诊断数据可能就丢失了。因此,任意一个小阶段的诊断完成时,我们就立即上报此结果,以避免此类情况的发生。
四、CDN质量监控
CDN作为整体白屏问题中重要的一环。在白屏排查过程中,经常遇到接口正常,但CDN访问异常的case。而多云CDN的质量问题往往依赖于云厂商,如何衡量和监控云厂商的CDN情况成为了关键,故我们自定义了端侧的CDN质量指标体系以及配合云厂商推进云端策略的优化。
CDN质量大盘
在白屏监控平台侧,我们经常能够发现用户侧的CDN单点问题,往往存在跨省、跨运营商调度的情况,或者是单节点的高负载响应超时。故我们基于此情况记录了端侧的本地IP&CDN远端IP,建设了CDN质量大盘,核心指标如下:
依赖CDN质量大盘,我们可以横向、纵向对不同云厂商服务进行CDN质量评估工作,由于端侧实际发起的总请求数量统计成本高、到CDN侧的请求存在丢失等情况,我们采用了端侧采样上报、统计还原的思路,将得物侧的整体CDN指标大盘采样放大比例为1 / 千分之三(当前采样比例)。过程指标的确会收到采样用户质量的影响,但整体CDN趋势、各厂商CDN的横向对比还是有很大的参考意义的。
平台截图展示:
省份、运营商
超时率、跨省率
节点情况
CDN云端监控
如果说端侧CDN指标是对CDN质量抽象评估的长周期指标,那么云端监控则是CDN质量的精确告警的短时效指标。
这部分就是一些较为常见的策略与手段了,如CDN节点拨测、CPU负载监控、异常错误码监控等,并协同SRE团队将其同飞书通知打通,实现及时告警。
节点拨测
CDN节点拨测整体指定的图片拨测阈值当前限制在200ms。
站点拨测
飞书通知
节点监控
关于到节点后的请求监控,主要依赖于云厂商的监控能力,我们也协同云厂商对核心指标的告警阈值等进行了调优,核心主要为:
此处思考一个问题,如果建连请求无法抵达CDN节点,那么即使CDN厂商开启了TCP级日志,也无法监测到此异常,也就无法及时进行调度调整。
故对于非到节点的建联问题,基于TCP建连进行监控是很有必要的,我们基于客户端建连的监控数据,以用户网络环境(网络运营商,地域)视角,分析建连过程是否正常,及时发现异常连接线路,并通过推动CDN服务商调整区域DNS返回来恢复正常。
监控指标如下:
建联失败、异常率
建联p50、p99耗时分布
五、网络问题优化治理
DNS策略优化
从网络监控、白屏监控中我们可以清楚的观察到DNS错误是网络阶段导致白屏的最大因素。此外,DNS错误是所有网络错误中最多的,甚至可以占比80%以上。
连续DNS错误导致白屏
LocalDNS行为
为什么DNS阶段如此脆弱,是我们使用不当,还是其本身性能如此?我们首要做的是观察下Android平台下LocalDNS的行为逻辑以确认性能较差的原因。
按照TTL缓存
通过不停的向系统进行查询DNS,在抓包中,我们很容易观察到DNS的查询逻辑。其中一个重要参数是TTL(Time to Live),这个参数表明当前查询结果的有效时间。比如说TTL=7,表明在7秒钟内当前结果是有效的、可被缓存的,即使7秒钟内再次查询,依然是此结果。
Android平台严格遵守了此协议,按照TTL进行缓存。而较短的有效时间,也意味着我们需要进行域名查找时(冷热启动),大概率系统内没有缓存可用。
DNS TTL与查询频率
通常TTL设置为120秒以内,这是因为要考虑到节点故障,能够快速切换。而到端侧查询时,TTL会在0~120之间。低于120是因为我们查询此结果的DNS server自身已经缓存了一定的时间。
双栈客户端同时查询
双栈客户端上,同时发起A类(IPv4)、AAAA类(IPv4)查询。
某运营商5G网络下DNS查询及响应
当两类查询都成功响应时,Android会将两类结果组合起来排序后返回给应用层。无论是A类地址先返回,还是AAAA类地址先返回,Android都会将IPv6地址放在前面(划重点,后面要考)。
-- app.dewu.com 的DNS查询结果
[app.dewu.com/2405:e000:1004:1::f3ff:4b49, app.dewu.com/203.107.32.125]
可以看到电信的LocalDNS服务器并未遵守TTL时间(app.dewu.com 的 TTL为60),而是进行了额外的缓存。这是部分运营商为了降低客户端的查询频率(降成本)而搞出的小把戏,不在我们的讨论范围内。
DNS数据包错误与重试
我们将DNS服务器设置为一个虚假的DNS服务器,来观察Android平台在DNS未返回时的重试逻辑。
DNS重试(主、备、隐藏款)
可以看到,0秒时向主DNS服务器发起查询,5秒向备DNS服务器发起查询,8秒向114发起查询。
现在我们想一下,在我们DNS查询时,只要0秒的第一次查询向主没有正常返回(比如丢包),即使备选正常,能成功查询到结果也是5秒钟后的事情了,而这意味着用户在5秒内无法正常的请求网络。然后,用户熟练的打开设置、点击用户反馈、选择白屏、输入些表达欣喜的文字、提交!(嘿,来活了)
那么我们能否通过多次查询来触发系统来同时发起多个查询请求呢?不行。当前Android内有一个同步栅栏,多个线程同时查询一个Host,只会允许一个通过。如果成功时,全部被阻拦的查询一同返回结果;如果失败时,再允许一个查询。也就是说,在这5秒内,我们只能静静的等着,让用户也等着。
主/备DNS服务器我们可以通过在设置中修改,而114这个隐藏款只出现在部分国产品牌上,不同品牌内置的隐藏备用DNS服务器也并不相同。
iOS平台的DNS行为
众所周知,iOS平台下的DNS异常率远远低于Android,甚至差了一个数量级。iOS为何如此优秀?
-
较长的缓存时长。当DNS的TTL较低时(比如3秒),iOS会忽略TTL值,直接缓存1分钟以上。
-
积极的重试逻辑。只要1秒内未收到响应立即进行多次重试,其中主DNS 6次,备DNS 8次。
DNS优化
通过对LocalDNS的行为分析,我们看到Android平台下的LocalDNS查询极其不可靠,好在我们并非只能通过LocalDNS来解析IP。任何将域名转换为IP的手段都可以使用,比如通过http获取、磁盘缓存等。
在LocalDNS的基础上,我们增加了HttpDNS,磁盘缓存来优化DNS问题。其中HttpDNS在第60ms异步启动,磁盘缓存在第6秒钟时异步启动。三种查询方式任意一个返回,则DNS结束。
HttpDNS
我们实现Http进行DNS查询,并将其作为备用DNS使用。在LocalDNS 60ms未成功返回时,异步启动HttpDNS进行查询。这将能解决LocalDNS下主DNS失败5秒后才会进行下一次重试的弊端。
HttpDNS的实现方式,我们暂时选择接入成熟的三方HttpDNS,快速解决用户在DNS方面的困境是主要原因之一。
当然,也并非全部域名通过HttpDNS查询都会有效果,比如localhost、IP地址等就需要从HttpDNS的查询中排除。
同步栅栏:当存在单个host的多个查询线程时,仅允许第一个线程查询,其他全部等待查询结果。(类似于Android系统对多线程同时查询时的处理)
磁盘缓存
DNS查询成功时,我们将查询结果缓存到磁盘上。在LocalDNS、HttpDNS都未如期返回时,将磁盘上缓存的结果返回给应用层。如此以来,只要历史上这个用户查询成功过,我们始终不会失败在DNS阶段。
需要注意的是,在磁盘上缓存IP时,需要按照网络类型、运营商进行缓存。原因是不同网络下的最佳IP往往不同,比如同一个CDN域名中国移动的CDN节点与中国电信的CDN节点会在各自的机房里。如果我们不分开存储,那么用户网络切换后,我们磁盘缓存出的IP或许可用,但可能不是最佳(延迟更高)。
此外,我们的磁盘缓存也完全忽略了DNS TTL。原因是我们将磁盘缓存作为其他DNS查询全部失败后的兜底手段使用,而非主要手段。即使TTL过期的IP可能会存在一些问题,但也不会有更坏的结果了。
收益情况
通过线上实验观察到,DNS异常率降低了60%以上。
不同配置下的DNS异常率对比
IPv6故障修复
前文提到对于双栈客户端,Android会将IPv6地址放在前面返回给应用层。本意上,IPv4地址即将枯竭而向IPv6过渡的方式,而这恰恰是Android平台在TCP建连阶段时遇到的最大问题。
用户A-DNS结果
我们来看一个例子,用户A访问 cdn.poizon.com 是DNS共响应了10个IPv6、10个IPv4,然而此用户IPv6无法访问,且给个IPv6都是建连10秒后超时,最终导致用户在第100秒时才在第11个IPv4地址上成功。
RFC6555 摘要
正如 RFC6555 摘要中描述的那样,当服务器的 IPv4 路径和协议正常,但IPv6 路径和协议不工作时,相对于IPv4单栈客户端,双栈客户端会经历显着的连接延迟。而这个延迟通常是10秒以上(IPv6的个数和建连超时时间的乘积)。
那么如何优化?
最简单的方式是禁用IPv6,简单到甚至只需要运维动动小手即可。但是由于众所周知的原因,我们国内对于推动IPv6十分积极,对于IPv6的浓度动不得。
最合理的方式自然是按照RFC6555描述的那样实现快乐眼球算法,但是开发成本和风险都较大。而线上时而有类似反馈。因此我们决定分两步走,先上线低风险的IPv6探测&重排序解压此困境,再实现快乐眼球算法。
OkHttp5.x 已支持RFC6555,但是目前尚未正式发布。且得物历史上对源码的修改较多,整体迁移到5.x成本较高。
IPv6探测&重排序
此实现的思路是既然IPv6可能故障,那么提前探测潜在故障。通过调整即将建连的IP列表的顺序来解决客户端建连到IPv4的延迟问题。
此实现在DNS查询成功之后,建连之前。因为不涉及修改OkHttp源码修改所以风险可控。
IPv6探测
如果IP列表中同时包含IPv6&IPv4,则对第一个IPv6地址同步进行Ping探测。如果250ms内探测成功或曾经探测成功,则认为IPv6正常。
重排序
如果IPv6畅通,则按照IPv6优先进行交叉排序。如果IPv6故障,则按照IPv4优先进行交叉排序。
# 重排序前
[
"imagex-cdn.dewu.com\/2409:8c54:b010:b:8000:0:b00:13",
"imagex-cdn.dewu.com\/2409:8c54:b010:b:8000:0:b00:10",
"imagex-cdn.dewu.com\/183.240.183.19",
"imagex-cdn.dewu.com\/183.240.183.15"
]
# 重排序后(IPv6探测失败)
[
"imagex-cdn.dewu.com\/183.240.183.19",
"imagex-cdn.dewu.com\/2409:8c54:b010:b:8000:0:b00:13",
"imagex-cdn.dewu.com\/183.240.183.15",
"imagex-cdn.dewu.com\/2409:8c54:b010:b:8000:0:b00:10"
]
# 重排序后(IPv6探测成功)
[
"imagex-cdn.dewu.com\/2409:8c54:b010:b:8000:0:b00:13",
"imagex-cdn.dewu.com\/183.240.183.19",
"imagex-cdn.dewu.com\/2409:8c54:b010:b:8000:0:b00:10",
"imagex-cdn.dewu.com\/183.240.183.15"
]
IPv6探测&重排序 流程图
快乐眼球(HappyEyeball)
当前DNS返回的IP地址数量超过一个时,每250ms异步启动一个新的TCP建连,任意一个TCP建连成功,则断开其他TCP并开始同步TLS建连。
TLS 建连成功则返回此Connection,流程结束;TLS 建连失败时,如果是可恢复的TLS失败(部分SSLException),则立即重试;如果是不可恢复失败,则再次启动其他TCP的竞速。
核心修改点是原流程的建连部分。原流程中,在网络线程中同步的、依次进行TCP、TLS建连。新流程中,将TCP与TLS建连分开,竞速建连TCP,TCP建连完成后建连TLS。
- OkHttp3.x 建连流程
OkHttp3.x 建连流程
注:OkHttp3.x 的nextRoute之间的循环在RetryAndFlow实现
- OkHttp-HappyEyeball建连流程
OkHttp-HappyEyeball建连流程
具体实现方式可以参考OkHttp5.x 代码
收益情况
自IPv6优化上线后,再无相关IPv6相关用户反馈。
六、CDN质量问题治理
从白屏监控大盘中可以看到,CDN的问题可能只占据1%~2%,但是不同于通用网络问题,用户凭持着其他App可以用,但是得物App为什么白屏的疑问,是极容易引起线上反馈的一种case。故我们也是协同SRE团队、云厂商一起做了一些优化手段,力争在现有条件下将端侧的图片CDN质量调整到最优。
返回IP策略优化
从网络优化来看,DNS优化以及IPv6探测&重排序为我们解决了双栈IPv6问题、单IP不可用等问题,但CDN问题本身由于不同省份、不同区域等节点数量、网络硬件质量均存在差异,这部分是端侧无法弥补的,故我们想到了优化CDN返回IP策略的思路来规避以上问题。
返回IP优化策略
过程中考虑到得物IPv6浓度问题,最早期是返回了3个v4 IP、3个v6 IP的策略,但发现v6 IP数量变多后,由于LocalDNS天然会把v6 IP排放在v4 IP前面。此举在没有建连竞速优化的老版本下,会导致v6不通的建连时间被逐步拉长30s。故我们在尽可能保证v6浓度的情况下,将返回的v6 IP数量降低到了2个。
由于v6 IP优先请求的情况,我们考虑优先保证v6 IP的本省同大区覆盖,故对v6 IP的本省的大区返回粒度会比v4 IP更细些。
- 跨省调度问题
针对v4 IP本省2个大区至少返回2个IP
针对v6 IP本省1个大区至少返回1个IP
- 单ip返回、v4不返回问题
针对v4 IP至少返回3个
针对v6 IP至少返回2个
云厂商优化策略
为了保证图片CDN的节点质量,我们积极同CDN厂商进行沟通协作,进行了部分优化尝试。
图片专属节点
将原有云厂商的图片、视频、文件等通用节点,迁移至图片专属的L1节点,最大程度保证图片请求稳定性和效率。
H2协议栈优化
过去发现请求耗时增大的一个case是H2弱网阻塞,在低质量的网络环境下使用HTTP/2协议时可能遇到的性能问题或阻塞问题。
云厂商均多次调整了H2协议栈的算法优化逻辑,并对比了优化前后的慢请求监控情况。
- 观察调整省份节点水平,调整后平均耗时下降15%、慢请求率下降23%左右。
请求耗时大于5000ms的优化情况
节点动态剔除细化
我们针对云厂商的到端链路监控以及白屏用户反馈的潜在非到端问题,通过平台归因1min内的CDN请求 & 网络诊断的情况,并经过人工确认为CDN单通问题后,进行节点剔除切换,优先处理节点抖动带来的影响面问题。
-
CDN节点的可用性探测判断节点持续抖动并最终剔除的时效在15min内;
-
白屏反馈用户的sop应急响应剔除时效从2~3小时降低到30min内。
CA证书问题
除了建连阶段的问题外,TLS等证书认证问题上也有较多的用户反馈,同时白屏平台也有较多类似的错误。
历史为了优化SSL耗时过长、证书认证安全等问题,接口、图片域名均开启了ocsp的功能,但实际监控发现部分用户存在本地时间不对齐的情况,即超过了CA证书 ocsp校验的有效期(一般7天) 或者超过了域名的证书有效期(一般2年)。此类用户会导致所有图片请求均被证书拦截而无法完成请求最终引发白屏。
ocsp问题
-
TLS耗时开启前后并无实际收益,对于网络耗时的优化可忽略不计;
-
证书的安全性问题,由于图片域名访问为CDN资源非接口核心信息,关闭风险可控;
-
对比主流站点的ocsp开启情况,主流App的图片域名大多未开启。
ocsp的当前时间和下次更新时间
故我们最终关闭了图片域名的ocsp功能,整体请求异常数、请求耗时并无劣化且图片ocsp的问题反馈数逐步清零。
域名CA证书替换失效问题
得物目前有100+的域名,CA证书的申请对于CA机构来说,一般在证书有效期小于30天时会进行新证书的签发流程,且证书有效期以申请期时间为准。但过往的证书有效期更新替换后,往往会面临时间前置的用户无法通过CA证书校验,最终引起用户白屏。
我们协同运维侧制定了较为严格的图片域名证书更新时效期,并在多个主域名按此标准落地。
-
证书30天到期自动告警能力,及时跟进证书审批事项;
-
证书替换选择在有效期提前3天,避免无规律的申请即更新的情况。
七、总结
网络问题作为用户白屏的主要原因之一,如果不能及时针对性的优化将对大量用户产生困扰。我们在以解决用户白屏为目标的行动中,逐步完善了客户端网络监控、云端CDN监控。并在此过程中完成DNS、建连、证书、运营商调度等方面的优化,杜绝了某些特定错误的发生,保障了更多用户的网络体验。
近期我们会继续推出一篇介绍如何在客户端监控白屏问题,以及平台如何对白屏问题自动化归因的文章,敬请期待。
*文 / 厉飞雨、GavinX、Jordas
本文属得物技术原创,更多精彩文章请看:得物技术
未经得物技术许可严禁转载,否则依法追究法律责任!