18.App稳定性专项实践
目录介绍
- 01.稳定性背景说明
- 1.1 项目背景
- 1.2 什么是稳定性
- 1.3 稳定性纬度
- 1.4 衡量标准
- 1.5 建设指标
- 1.6 稳定性建设收益
- 02.稳定性现状介绍
- 2.1 影响稳定性因素
- 2.2 兼容性测试说明
- 2.3 稳定性总流程
- 2.4 稳定性建设总纲
- 03.事前预防目标
- 3.1 稳定性意识
- 3.2 设计良好架构
- 3.3 功能稳定性
- 3.4 工作流程保底
- 04.事中系统监控
- 4.1 异常监控
- 4.2 业务链路转化监控
- 4.3 日志抓取系统
- 4.4 性能监控
- 4.5 耗时监控
- 05.事后排查实践
- 5.1 快速止损
- 5.2 问题周知
- 5.3 原因定位
- 5.4 配置降级修复
- 5.5 重新发版
- 5.6 总结与复盘
- 06.稳定性方案落地
- 6.1 架构演进
- 07.稳定性优化总结
- 7.1 做了哪些优化
- 7.2 如何保证稳定性
- 7.3 保障业务稳定性
- 7.4 异常快速止损
01.稳定性背景说明
1.1 项目背景
- 各种各样的问题频繁报漏
- 随着团队和业务快速增长,用户量的提升,线上问题多次发生,耗资源、失用户,稳定性的要求也越来越高,必须思考改如何从稳定性的角度来保证我们的质量。
- 稳定的软件系统是研发工作的职能和责任
- 业务的复杂度提升和用户设备的差异性也给客户端的稳定性和代码的可维护性带来了很大的挑战。客户端稳定性建设工作在业务发展的过程中越来越受到重视。
- 稳定性优化就是降低Crash率吗
- 但其实稳定性优化还有一个重要的维度就是业务的高可用。业务的不可用可能不会导致崩溃,但是会降低用户的体验,从而直接影响我们的收入。
1.2 什么是稳定性
- 这里引用一句对系统稳定性的定义:
- 系统稳定性指系统要素在外界影响下表现出的某种稳定状态。
- 对于客户端来说,从 App 的使用者和开发者这两个角度来说,稳定性的含义有所不同:
- 对于用户,稳定性意味着:使用 App 的过程中不崩不卡,能正常提供用户所需的服务,出现异常情况时引导合理,提示友好。
- 对于开发者,稳定性意味着:线上整体性能指标达标,核心业务链路稳定不出错,非核心业务异常时有出口、可降级。
1.3 稳定性纬度
- 很多人都会认为稳定性优化就是降低Crash率,但如果你的APP没有崩溃,但是关键功能却不可用,这又怎么算是稳定的呢?
- 因此应用的稳定性可以分为三个纬度,如下所示:
- 1、Crash纬度:最重要的指标就是应用的Crash率。
- 2、性能纬度:包括启动速度、内存、绘制等等优化方向,相对于Crash来说是次要的,但也是应用稳定性的一部分。
- 3、业务高可用纬度:它是非常关键的一步,我们需要采用多种手段来保证我们App的主流程以及核心路径的稳定性。
- 所以结合上面的关注点汇总来说,客户端稳定性可定义为以下几个方面:
- 崩溃稳定性
- 性能稳定性
- 业务链路稳定性
1.4 衡量标准
- 有个定义之后,就需要来考虑如何衡量的问题了。衡量系统稳定性从可量化的角度来说,当然离不开稳定性指标。
- 客户端的稳定性衡量,从目前已有的性能稳定性指标来说的话,一般从问题的影响面和问题的严重程度这两方面来衡量。
- 以最常见的客户端 Crash 率指标为例:
- 影响用户占比 Crash 率(影响用户数 / 使用用户数)是从 Crash 的影响面来衡量。
- PV 维度的 Crash 率(Crash 次数 / 使用用户数)是从 Crash 的严重程度来衡量。
- 例如 Crash 率在千分位的占比算是合格的,万分位的占比一般认为是良好。如果是百分位表示需要改进。
- 对于性能稳定性来说,
- 对于业务链路稳定性来说,目前有一些业务通过监控的方式来做稳定性建设,其中包括
- 对基础服务的监控,例如业务依赖的音视频调用的 PV / UV 以及对应维度的失败率等
- 对业务链路的监控,例如核心业务链路相关的调用、 API 请求的 PV / UV 以及对应维度的失败率,异常率等
1.5 建设指标
- 基础指标:
- 质量保障工具落地客户端,降低问题发生概率,减少线上问题。建设稳定性相关工具,提高测试效率
- 在稳定性建设中,以【故障发生时】为参考点
- 一般把处理阶段分为【事前】(故障发生前)、【事中】(故障发生时)和【事后】(故障发生后)这三个阶段。
- 针对这三个阶段我们可以分【预防】、【发生】、【解决】和【总结】四个方面来建设,每个方面都有各自的职能和侧重点,但也相互关联相互依赖。
1.6 稳定性建设收益
02.稳定性现状介绍
2.1 影响稳定性因素
- 人为因素
- 通常人为因素都是可以在事前避免的,因为它是可控的。人为因素的故障发生后一般都需要修复后重新上线才能止损。
- 例如典型的人为因素有代码编写错误、配置错误、系统设计不兼容等等。
- 自然因素
- 自然因素通常属于概率事件,受客观因素影响,不受控,无法避免。例如环境因素(网络环境、地理位置等)和外部依赖因素(第三方故障)等等。
2.2 兼容性测试说明
- 不同厂商的ROM与各种机型是否兼容
- UI展示是否完整,是否重叠
- 不同机型对第三方库的兼容性
- 不同机型本身的特性(导航栏,状态栏)
- 不同机型的手势冲突
- 不同机型的权限控制
- 不同语言环境的功能和UI展示。yes
- 不同系统时间下的功能和UI展示
- 不同主题下的功能和UI展示.
- 不同输入法对输入框的功能和UI展示.
- 不同的web 引擎
- 弱网络测试
- 加载中的状态
- 加载失败的状态和文案
- 重复请求
- 重连后的网络请求
- 网络切换后功能是否正常
- 断网重启
- 其他场景模拟
- 前后台切换 yes
- 常置后台 yes
- 劣化模拟(高内存,高CPU消耗)
- 暴力测试
- 不同路由堆栈下的场景(Flutter→Native N→F →F →N ... )切换之后的UI元素及操作
- 滑动异常(多点触控)
- 数据异常(list 元素为空,长文本)
- 状态快速切换
- 覆盖安装测试 yes
- 测试手段
- 手工测试
- 自动化测试主要是利用脚本对被测应用的安装卸载,稳定性,功能等的测试。
2.3 稳定性总流程
- 事前
- 方案评审,代码Review,技术串讲,准入自测,灰度流程
- 事中
- 观测发版数据
- 事后
- 线上问题分析模板,增发,复盘机制,sdk降级,Crash防护
2.4 稳定性建设总纲
- 事前预警
- 监控触发报警 -> 有充足时间应对
- 分层监控:系统级监控、性能指标监控、业务监控、健康度分析(指标变化趋势)
- 监控曲线:根据业务流程监控,以识别出现问题的环节
- 事故处理
- 第一原则:及时止损;因发版导致的问题,则及时回滚
- 限流:防刷、等待+限时、单机 QPS 保护(白名单放过关键路径,如下单、支付)
- 保护用户体验:客户端配合降级;力保关键路径:非关键路径模块降级
- 事后总结
- 必须找到根源:采用 5whys 分析方法从现象开始追踪到最根本的原因
- 核算造成的损失:计算稳定性
- 事故总结,如何优化:系统改进、流程改进、开发红线
- 总结的意义:看到问题,采取措施,以便在将来再次遇到问题时处理地更快更好
03.事前预防目标
3.1 稳定性意识
- 稳定性需要大家共同来建设。因此提升大家的稳定性意识,是稳定性长治久安的第一步。
- 可以通过日常的规范、机制来提升大家的稳定性意识。例如定期的双周会或月会机制来通报和讨论稳定性相关的问题、进展和成果,拉齐认知。
3.2 设计良好架构
- 稳定性建设一定离不开良好的架构设计。好的业务架构应该具有容错性和可扩展性的。这里有几个原则可供参考:
- 解耦合:模块拆分,各自独立。这也是软件设计中一个非常重要的原则。
- 依赖稳定性原则:拆分易变的和不易变的模块。易变的模块可能改动频繁,因此要隔离对不易变模块的影响。
- 保障体验的容错设计:出现异常情况时对用户的引导和提示。这一点在日常工作中可能比较容易被忽略,因为异常情况通常被认为是概率事件、出现的可能性小。但其实当故障发生时,好的引导能快速弥补系统异常带来的产品缺陷。
3.3 功能稳定性
- 在日常的需求迭代中,稳定性应作为功能交付的一部分,而不是先只交付需求 feature 本身再回过头来专门花时间治理稳定性问题。可执行的方案有:
- 技术方案设计主动包含稳定性考量:在设计技术方案时就将影响稳定性的因素考虑进去。
- 功能开关:对有风险的功能增加开关。
- 代码提交时的 CR 把控:小范围内组织当面讲代码,可按照工作量来决定是否启动此流程。例如工作量大于 3 天的需求需要组织小组内 CR 会议。
- 阶段性的模块级 CR:月度组织模块级 CR 会议,组内同学相互检查、监督代码是否遵循编码规范,模块设计中是否存在缺陷,同时也能相互学习好的编码风格和设计思路。
3.4 工作流程保底
- 在客户端稳定性建设中,研发作为需求的实现方,势必要肩负起更多的职能和责任。在研发侧完整的开发流程应包含以下内容:
- 技术方案的设计、评审
- 编码,Code Review、研发自测、前后端联调
- 提交测试,Bug Fix,版本发布、线上灰度放量,各项监控指标观测直到版本全量
- 在这个流程中,每个环节都应该有明确的要求和行为规范
- 同时也需要一些工具来辅助建设,例如代码规范检查工具来约束编码风格、CR 工具来完成代码审查、质量管理工具来收集和管理开发期间的问题、提测管理工具来约束研发自测等等。
04.事中系统监控
4.1 异常监控
- 异常监控是从异常的角度来配置监控。例如接口请求的失败率、失败的PV、UV 等等。
- 这种方式主要是通过大盘数据展示各个模块的异常情况,如果发现异常指标则进一步分析问题原因。同时根据实际情况制定监控指标的参考值,有了监控指标参考值之后,就可以配置异常报警。
4.2 业务链路转化监控
- 相对于异常监控来说,业务链路转化监控则是从正向的角度来看大盘情况。
- 我们可以通过对业务链路上的关键节点打点,来实现对业务链路转化情况的跟踪。例如在用户下单的流程中,在下单和支付环节打点,通过转化率来监控下单流程中是否出现了阻塞性问题。
- 业务链路转化监控可以更加清晰、直观地看到影响业务流程正常运转的节点,从而做到有的放矢。
4.3 日志抓取系统
- 日志系统的主要功能是用来在故障发生时做场景还原,帮助我们分析故障产生的具体原因。
- 日志由用户行为和系统行为 + 数据产生(请注意排除用户隐私数据) → 日志被记录在用户设备上、后台系统里 → 后台系统里记录的日志可查询、展示 → 当主动抓取用户设备上的日志时也可从用户设备上传到服务器。
- 日志系统在客户端的日常工作中使用也比较多,这里再做几点补充:
- 客户端的日志文件大小和设备网络因素对日志文件上传有影响
- 从日志写入规范来说,需求考虑日志分级记录。例如常见的 Verbose、Debug、Info、Warn、Error 日志分级定义。业务日志分模块记录。
- 当出现客诉问题而客户端没有对应场景的日志时
- 如果是因为日志等级没有写入文件(如 Debug 级别的日志在 Release 包中默认关闭),可将日志等级作为线上开关来配置。
- 如果是因为日志本身写入缺失,那需要考虑动态下发收集日志的能力来支持。
4.4 性能监控
- Crash 率指标衡量。
- 常规 Crash:灰度期间发现时要高度重视。通常按照影响程度决策修复方案。例如热修复、重新发版等。
- 启动 Crash:对用户伤害大,可能无法通过热修复方式来解决,需结合其他自救方案。例如安全模式等。
- 卡顿率指标衡量。
- 卡顿是用户操作流畅度的一种负向表现,通常和 UI 线程操作、内存、线程等问题相关联,比较复杂。
- 卡顿排查主要通过系统及开发平台提供的工具。
- 内存问题,内存问题通常包括内存泄漏、内存溢出等。内存溢出问题要高优处理。
- 内存泄漏在开发期间应及时处理。对于共性的内存泄漏问题需要思考通用的解决方案。例如 Callback、Listener 与组件生命周期绑定后自动释放。
- 内存泄漏问题比较困难的点是发现。除了人为定期去排查的方式成本较高以外,不同的业务场景和操作流程下暴露的内存泄漏问题是不一样的。仅凭单点的视角很难看到内存泄漏问题的整体情况。因此内存泄漏的相关问题可以做 APM 平台化管理。
- 内存溢出通常跟内存泄漏、大图资源加载、线程创建、文件操作异常相关。这也是内存问题最坏的情况,在用户侧表现为应用崩溃。
- 网络问题,网络问题也有对应的网络异常指标来衡量。
- 通常与服务器异常和网络环境相关。服务器相关问题由后端和运营来处理。客户端相关问题则需要结合网络库来统一优化。
4.5 耗时监控
- 耗时监控主要是指什么内容
- 1.业务衡量指标建设。举一个例子,比如你去超市买东西,扫二维码支付,那么从识别二维码到支付成功的消耗时间是多少,有什么衡量标准。
- 2.一些关键函数耗时。举一个例子,应用中有很多数据库增删该查的工作,那么如何衡量数据库业务稳定性。可以监听函数执行耗时,检测是否有卡顿现象。
- 目前打点统计耗时方案有多种
- 第一种:直接用代码标记当前打点时间,然后在打点结束处,计算与之前点位的差值。对代码侵入性非常的大。
- 第二种:在项目核心部分添加打点,插桩也是可以的。比如统计activity启动耗时,但这种不适合业务打点统计耗时。
- 第三种:统计耗时封装,使用Map集合存储打点情况,定义start和end方法,然后在end方法中计算耗时时差。使用业务耗时统计。
- 第四种:拦截器耗时统计,借鉴拦截器思路,可以用于拦截核心方法的耗时逻辑,以及异常。使用函数耗时统计。
- 具体看:https://github.com/yangchong211/YCAndroidTool/tree/master/MonitorTimeLib
05.事后排查实践
5.1 快速止损
- 止损的方法依赖我们事前的诸多工作,例如:
- 关闭功能开关;非核心依赖降级;切换网络通道;拒绝请求处理
- 这些止损的方法都是在预防阶段准备好的,当故障发生时只需要按既定的流程来执行即可,这样才能在第一时间将损失降到最低。
5.2 问题周知
- 线上出现问题后要及时周知相关方,方便大家共同协作来解决问题。另一方面,问题处理的进展也会影响相关方的工作安排。例如前线运营、客服团队需要对问题的原因和进展有一定的了解,以便统一对外回复、准备应对措施等。
5.3 原因定位
- 当故障的影响得到控制之后,就需要定位问题发生的原因了。在问题定位的过程中,有以下几点需要注意:
- 多人相互协作,提高效率。需要有一个协调者来统筹,避免多人在做重复的工作。
- 问题发生的直接原因和根本原因可能有所不同。这一点对解决方案的决策有较大影响。
5.4 配置降级修复
- 当我们做了大量的前期稳定性建设工作之后,大部分问题都应该通过配置降级的方式来解决。对于客户端的发布形式来说,这是问题得到快速解决、线上生效时长最短的处理方式。
- 在实际的操作中,需要注意的是配置降级具体涉及哪些开关、配置,如何正确操作,是否会影响其他依赖。这里建议加入双重确认机制,避免因人为操作失误引入新的问题。
5.5 重新发版
- 客户端重新发布版本对用户影响较大,且生效时间漫长(这其中包括了应用市场审核的时间、版本重新放量的时间、以及用户设备上的版本最终更新的时间)。在采用这种方式时:
- 研发要基于发版代码重新构建修复问题的版本,同时修改版本号,并完成自测。
- QA 验证新版本,回归核心功能。
- 双方验证完成后,提交应用市场,等待审核。
- 审核后配合灰度机制,观察线上情况,直到恢复正常。
5.6 总结与复盘
- 总结与复盘的目的在于发现需要优化和改善的地方,从而在后续工作中更好地预防和处理。首先,请正确认识复盘的意义,并努力克服归咎他人的本能。
- 当故障被修复后,通过系统性的总结和回顾故障发生时所采取的措施,能帮助我们去发现现有系统、流程中存在的问题,同时思考对应的解决方案。通过不断地完善整个系统和流程,才能避免后续同类型问题的再次发生。
- 复盘时通常需要详细记录故障发生时具体的时间点由谁采取了什么样的措施,这种形式的场景还原有助于我们分析和判断当时的措施是否恰当,有没有影响到故障的及时处理,是否有可优化的空间等。
06.稳定性方案落地
6.1 架构演进
- 耦合的服务 → 服务化拆分(服务解耦、数据库解耦)
- 引入中间件
- 做好稳定性架构设计,几个原则:
- 系统(模块)拆分,各自独立
- 依赖稳定性原则:拆分易变的和不变的,易变的服务随时发版、不变的服务保持稳定
- 保障用户体验的容错设计:出现异常情况时用户的体验和引导
07.稳定性优化总结
7.1 做了哪些优化
- 针对稳定性开启了专项的优化,我们主要优化了三项:
- Crash专项优化(=>2);性能稳定性优化(=>2);业务稳定性优化(=>3)
7.2 如何保证稳定性
- 性能稳定性是怎么做的?
- 全面的性能优化:启动速度、内存优化、绘制优化
- 线下发现问题、优化为主
- 线上监控为主
- Crash专项优化
- 优化分为两个层次
- 优化主要分为了两个层次,即线上和线下,针对于线下,侧重于发现问题,直接解决,将问题尽可能在上线之前解决为目的。
- 而到了线上,最主要的目的就是为了监控,对于各个性能纬度的监控呢,可以让我们尽可能早地获取到异常情况的报警。
7.3 保障业务稳定性
- 业务稳定性如何保障?
- 数据采集 + 报警
- 需要对项目的主流程与核心路径进行埋点监控
- 同时还需知道每一步发生了多少异常,这样,我们就知道了所有业务流程的转换率以及相应界面的转换率
- 结合大盘,如果转换率低于某个值,进行报警
- 异常监控 + 单点追查
- 兜底策略
- 业务高可用侧重于用户功能完整可用
- 需要对项目的主流程、核心路径进行埋点监控,来计算每一步它真实的转换率是多少。
- 开发过程当中代码中出现了一些catch代码块,捕获住了异常,让程序不崩溃,这其实是不合理的,程序虽然没有崩溃,当时程序的功能已经变得不可用,这些被catch的异常也需要上报上来,这样才能知道用户到底出现了什么问题而导致的异常。
- 线上还有一些单点问题,比如说某用户点击登录偶发一直进不去,这种就属于单点问题。需要通过核心链路日志排查原因。
7.4 异常快速止损
- 西瓜视频稳定性建设
- https://juejin.cn/post/6953430618726203399
- https://mp.weixin.qq.com/s/DWOQ9MSTkKSCBFQjPswPIQ
- 货拉拉Android稳定性治理
- https://juejin.cn/post/7100743641953468452
- https://blog.51cto.com/u_15233911/2826427