客户端崩溃治理长效机制

2023年 8月 9日 27.2k 0

前言

客户端崩溃治理长效机制是一套系统化的方案,属于基础架构可观测性指标的一部分,用于对客户异常崩溃、卡顿、错误等快速发现、第一时间预警提醒、快速协助定位分析,从而帮助开发人员快速及时发现和解决客户端相关问题,从而提高用户体验,保障业务服务高可用,减少因使用过程中出现异常而导致的用户流失,提升品牌技术价值。

一、背景

去哪儿网是一家典型的在线旅游平台,随着去哪儿业务快速发展,现在已经涵盖机票、酒店、火车票、门票、旅游度假、汽车票等十几个业务线,细分业务多达近 40 个,有业务更新迭代快,每天仅 QP (QunarResourcePackage:ReactNative 和 H5 的离线资源包)的发布次数就多达七八十次;加上开发人员更替、多种技术栈混合使用,产品逻辑复杂多变等特点;再加上用户复杂的网络环境及碎片化的操作系统,怎样保障用户流畅稳定的使用体验,减少异常崩溃而带来的业务服务中断、公司品牌口碑变差、用户留存率下降等问题是一个比较大的挑战。

image.png

二、解决方案

为解决上边提到的问题,我们框架团队在疫情期间以降低客户端崩溃率,使移动端质量达到优于行业标准水平为目标,搭建了一套客户端质量保障机制,并常态化执行下去,进而使移动端质量长期稳定保持在较高水平。首先,我们围绕怎样快速发现新问题,完善 APM 应用性能监控体系(以下简称 APM),从多个维度进行检测,防止新问题扩散蔓延酿成故障;其次发现问题后,通过什么样的机制,让业务和框架团队分工协作,快速解决问题;然后就是解决完问题后,通过什么样的技术手段提醒开发和测试人员,及时修复漏洞,避免该问题再次出现;这个思路也是我们保障机制的关键要素,下边我将按照这个思路带大家看我们每一步如何实施的:

image.png

1.快速发现问题

apm 系统每天收集到的卡顿、错误、崩溃等日志信息上万条,很多是已修复、系统相关、无明确调用堆栈等无法修复的异常,怎样快速发现哪些问题是新增的、有进一步蔓延可能、需要我们重点关注的问题,并且能快速定位通知到跟该问题相关的业务或框架同学,就需要我们在精准分析、归类、标记区分是否已修复或忽略的状态,以及快速预警上做工作;

APM系统对上报上来的异常数据数据进行准确分类

  • 首先,我们需要根据出问题的堆栈提取引起关键信息,排除内存值等干扰信息后进行聚合分类;

  • 其次,根据 Android native、so 库、ios、ReactNative 等不同异常类型,对错误堆栈进行反混淆、符号明文化处理,让开发可以看到堆栈就能知道定位哪块代码;

  • APM 系统与构建打包平台 MPortal(大前端打包发布平台)数据打通,获取每个 lib 库包名、so 资源列表、ios 页面列表、业务负责人等,精准划分业务线,获取报警联系人;

  • 降噪:我们动态配置 hook/xposed 等关键字,过滤掉爬虫黑名单数据,减少因误报而造成的数据干扰;

image.pngAPM架构图

快速精准预警,第一时间发现问题

【1】首先“防微”,在细粒度上进行监控:
应用运行上报的异常到达 APM 后,都会提取异常信息(lib库、异常类型、关键堆栈)进行聚合生成 BugId,判断该 BugID 之前30天内是否有过上报,如果没有则作为新增崩溃,过去一段时间影响用户数是否超过一定阈值(崩溃2个/5个、卡顿10个/25个),如果有则通过 QTalk(Qunar 内部即时通讯软件)、电话等进行报警,这样就做到防止微小异常扩大成大的故障;

image.png

【2】其次“杜渐”,针对历史问题,暂时忽略观察的问题,为防止存量问题积少成多、某刻或某天突然增加,我们从以下两个方面进行粗粒度的监控和报警:
粗粒度监控——Watcher:我们每个异常崩溃上报,都会调用 Watcher 监控系统进行打点,超过一定阈值就会报警,如下边这个是针对 Android 客户端 fastjson 库的异常监控指标图

image.png
异常上报量在 14:22 分时触发 3%10m>0.2 阈值报警(10分钟内有三次超过0.2次/秒),收到如下报警信息。关于 Watcher 系统更多介绍,可以参考我们另外一篇可观测性文章《去哪儿网企业级监控平台-Watcher》
粗粒度监控——总量监控:Watcher 系统更多监控的是实时指标是否超过阈值,如果是缓慢上报,积少成多也有可能需要关注,而 Watcher 瞬时量没有达到阈值就不会报警,因此我们增加总量监控,每个异常上报,我们都计算一下当前异常所属业务库今天的影响用户数是否超过过去 7 天均值的 50%,如果有则发送消息、拨打电话提醒相关人员关注。如下边这个警告,就需要定位 sight 库哪个崩溃问题引起的增长了:

image.png
另外我们还会通过日报,双周报、达芬奇(数据管理系统)看板的形式,展示昨天和过去两周 Top 新增崩溃信息,方便业务开发和值班同学查看本业务最近崩溃变化趋势。

image.png

image.png
同时,我们也把客户端质量作为可观测性指标、数字量化业务开发质量的一部分纳入我们研发数字化平台,来数字量化框架和业务团队开发质量和用户体验,把用户体验的感性认知,变为数字量化认知。对开发团队来说,质量高低用数字说话;通过变化趋势及时发现质量薄弱点规避风险,改进优化效果也可追溯;下图是国内酒店开发团队,在稳定性指标上得分变化趋势。

image.png
总结:粗细力度互补、多重策略、多通道通知,保障异常能第一时间发现并通知开发同学,防止扩大影响范围,进而引发故障;加上我们框架团队每半月复盘一次,整体分析治理效果,梳理后期改进计划。下边给大家介绍一下发现问题后,我们通用的解决方案。

2.通用问题解决方案

新老版本更新策略优化

在解决实际问题的过程中,我们发现有一部分是几年前的老版本客户端上报上来的,原因有老版本本身存在的缺陷、后端服务下线或升级新功能导致不兼容等;我们开始采用的解决方案:

1、下发补丁包:仅适用于 ReactNative 等可热发修复的场景,但是需要切换开发环境、发布区间生效版本、用新老版本进行验证等,测试成本较高;

2、服务接口兼容:如果是接口变动造成的,可以通过服务接口兼容,但弊端是服务长时间不能下线,兼容越来越复杂,维护成本很高;

3、强制升级:这是最直接的方案,通过强制用户升级到最新版本来修复已知问题,虽然会对用户造成一定影响,但长远来看使用新版本的使用体验和收益大于老版本;因此我们针对新老版本做了一些更新策略优化:引导两年前的用户强制进行升级,一年到两年之间的版本非强制引导升级,一两年内的尽可能减少升级提醒,以避免对用户过多打扰;同时针对与厂商合作的单独拉分支开发和发布的预装版本,部分定制功能需求未经过严格测试和线上用户验证,为避免发版后出现质量问题无法第一时间修复的情况,我们做了规范,所有预装适配如 Android 新系统适配,targetSDKVersion 升级等,都必须经过发公版验证 OK 后方可交付,解决了预装版本质量差、升级周期长的问题。

新版本按照崩溃类型进行治理

针对线上新版本出现的问题,我们进行分类,针对不同分类采取不同的解决方案:

  • 支持热发布代码:针对 ReactNative 这类支持热发布的问题,往往通过动态下发资源包即可修复相关问题,更新比较及时快速;为此我们增加业务线积分机制,该类问题要求业务线24 小时内进行处理,并标记解决状态,否则进行积分扣除,强化业务对开发质量的重视程度;

  • 堆栈明确的原生代码:如 Java、So 等需要发版才能修复验证的问题,堆栈明确的问题,我们一般采用尝鲜用户优先灰度升级进行验证,灰度发布期间发现的问题修复后方可上线,灰度崩溃率超过千分之一就自动阻断发布;

  • 没有明确堆栈、各个业务都有的共性问题:我们框架会立项专项治理,通过加强基础数据采集和监控、分析用户反馈和日志、分析交互轨迹进行溯源、添加保护逻辑等手段进行治理;

这里我列举两个典型的、没有明确堆栈的问题,以及我们框架采取的解决方案,给大家介绍一下我们如何攻坚解决共性问题的:

1、URL格式不合法异常处理

我们开发过程中,经常遇到异步线程发生的崩溃无法判断来源,比如端上发起的网络请求,但从崩溃堆栈来看,不知道什么地方发起的;也无法扫描代码确定出处,因为 URL 可能来自后端服务下发的,如果配置错误,在异步线程请求的时候就会导致直接崩溃。例如:后端下发的 url 是“www.qunar.com”,缺少 scheme 协议头,用 okhttp 请求时崩溃堆栈如下:

6b7117fadaf30be3c27f177cdfa36c4.png
这种情况往往回调网络错误即可,没有必要直接崩溃,并且这个崩溃出现在 okhttp 库中,没有业务代码,我们没法直接 try catch;查找了一下,网上也暂无好的解决方案供我们参考,最后我们通过分析 okhttp 的各种使用方式,以及抛出异常的代码逻辑,通过 Android Transform + ASM 字节码技术,在 okhttp 中所有 url 方法内部挂钩子,通过自定义的 MethodVisitor 的 onMethodEnter 方法在 okhttp 的 url 方法体内插入我们的 url 检查代码,如果发现 url 格式错误,则替换 url 为一个格式正确,但请求不成功的 url。代码示例如下:
okhttp3.Request$Builder.class 源码:

c777a0bae479c6c74ffbc0a3fd5ee9a.png
经过我们 URL 处理 hook 之后代码:

50694a3198c80b3dfb349521dae8cd4.png
HttpUtils 工具类中对 url 进行格式校验,校验失败记录错误上报,并返回一个 404 的 url,避免继续执行导致程序崩溃,同时可以让调用方可以按照网络错误处理。

7e6d83360161ca9c849ecc464c82827.png

2、Android Bundle中序列化大量数据导致崩溃问题

我们在集中解决大头问题的时候,遇到一种崩溃同样没有任何应用堆栈,崩溃量占我们整体崩溃量的 3% 左右,它与上一个崩溃不同的是系统在 ActivityStop 的时候调用序列化造成的,查看系统源码大概意思是 Android 进程中共享 bundle 数据的大小限制,当 Binder 进程中的 buff 数据超过 1 MB(官方说法,但是具体 rom 会有不同的限制),就会抛出 TransactionTooLargeException 异常,初步定位是页面间传递数据量太大导致,但又不知道什么数据有问题,如下堆栈:

image.png
因开发过程中很难复现,因此我们采用分部解决的方案,第一步采集数据补全埋点监控,第二步分析具体数据,判断具体数据大的原因,从根上进行解决,具体做法如下:首先我们采集每个页面在停止前 bundle 中序列化的数据 key 值以及对应的数据大小进行上报,最后洗数分析 Top 大数据页面和保存的数据进行解决。数据采集时机是在 Activity 的页面跳转前 startActivityForResult 方法和页面置于后台保存页面数据时 onSaveInstanceState 方法中对 intent 和 bundle 中的数据大小进行统计,相关统计代码如下:

307a2ceb9be7c93b672f5f03eb31cf6.png
分析采集的数据,我们发现传递数据大小并不是引起崩溃的主要原因,主要原因是页面消失调用 onSaveInstance 方法序列化数据时,view 树结构复杂、层级较深,保存页面 View 树 android:viewHierarchyState 数据太大导致崩溃,针对出问题的 Top 页面进行专项治理,用线上数据+框架技术支撑,驱动业务改进优化,至此新版本该崩溃将至个位数。 下边是我们酒店首页采集数据示例:

image.png
类似的处理方式还有很多,可以使得在业务线不修改源代码的基础上统一进行定制和优化,比如多线程异常往往不知道是哪个地方创建的线程导致的问题,我们会用字节码处理技术,在创建线程的地方,添加当前类名作为线程名字;还有网络性能埋点、安全隐私合规等,从框架层面灵活、高效、安全易用、不入侵源代码的方式快速解决同类问题;

3.检测工具

针对已经出现的问题,为了避免再次出现,如内存泄露、卡顿问题,通常比较难定位,但是为了方便定位问题,提前预防,我们框架团队开发了代码检测工具,比如 JS 内存泄露问题,URL 配置错误问题,我们在开发版本上集成 LeakCanary 等,在测试版本上判断是否有未释放的监听,直接弹框提醒开发和测试同学注意当前问题,下边是 JS 页面退出后没有移除监听的警告:

image.png

4.代码检查

在合并代码,集成测试阶段:增加 SwiftLint、Sonar、lint、Eslint 等代码检查工具、可以在代码提交、编译和发布前进行扫描,发现并报告规范、逻辑、质量和安全方面的问题,规范代码审查和集成流程,不仅帮助开发人员编写出更加健壮高质量的代码,同时如果检查不通过则不能进行线上发布,保障上线代码质量。在编译正式发布包的编译期,也会针对新增 warning 级别以上日志进行阻断发布、线上发布增加强制灰度验证等手段,提醒开发人员关注,以保证产品发布质量; 下图是代码提交 sonar 检查报告,包含阻断、严重、主要次要等问题数量,以及新增数量。

image.png

5.自动化测试

最后我们为减少人力测试成本,对主流程进行自动化的验证,项目打包完成后接入 TARS-UI 自动化测试系统,通过录制回放,自动执行测试工作,详细原理可查看崔宇同学分享的《Qunar 基于自动录制的 Tars 系统演进》。

image.png

三、成果展示

通过我们的客户端质量保障机制,我们成功解决了客户端质量问题,减少了异常崩溃率,提升了品牌形象和用户满意度:首先是崩溃率 Android 端从高峰时的 0.15%,下降到 0.02% 左右,IOS 端从 0.1%,下降到 0.02% 以下;明显优于行业平均崩溃率(根据 2020 年度移动行业性能体验报告,App 行业平均崩溃在 0.29%,Android 端行业平均崩溃率在 0.32%,而 iOS 端应用行业平均崩溃率在 0.10%);其次,通过我们解决问题总结下来的这套长效机制,能够快速的发现问题、遇到问题能通过通用的解决方案进行解决,然后通过检测工具、代码检查和自动化测试等手段保障已经发现的问题不会再次大量爆发,通过这样消山头,肯大头的方式,有效提高了客户端的稳定性和可靠性。

image.png

image.png

四、未来展望

客户端崩溃治理长效机制是我们基础架构“可观测性”指标的一部分。只有通过客户端崩溃治理长效机制和“可观测性”的双重保障,我们才能够更好地预防、监测、定位和修复客户端崩溃问题,保证软件开发的顺利进行,为用户提供更好的体验。未来我们将更加专注于用户体验和监控体系优化,通过技术手段智能分析同类问题共性特征,快速预警诊断,帮助开发人员快速定位问题;同时建立问题知识库,在解决问题时,标记导致该问题的原因及规避方案,其他开发查看同类问题时,系统自动给出参考解决方案,助力开发更高效,用户体验更好。

以上就是本次分享的所有内容,最后为大家带来一个内推信息,欢迎优秀的你加入驼厂~

【内推链接】:app.mokahr.com/recommendat…

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论