京东金融App崩溃治理实践

一、前言

在2020年初,京东金融App的用户规模已远超几年前,日活也是成倍的增长,同时我们也注意到App崩溃率随着版本迭代一直在小幅度升高。当我们意识到App的崩溃已经伤害到用户日常的使用体验时,崩溃率已达到千分之几。崩溃率是衡量App质量的重要指标,不仅影响App的稳定性,还会直接影响用户体验和业务增长。如果启动时发生崩溃可能导致App直接被卸载,进一步造成口碑变差、品牌价值下降等影响。所以金融App在业务高速发展的同时也更加的注重质量建设。

京东金融App的崩溃率上升与App业务的高速发展是分不开的。越来越复杂的业务场景、多个业务之间的逻辑耦合以及App功能的扩展都使程序更容易出现错误。一些古董代码在多次业务迭代之后慢慢受到了影响,在某些特殊场景下出现的错误需要很长时间并且是大量的用户使用时才会出现,这些都使得错误的修复变得不那么及时。灰度中出现的崩溃问题在查找无果后就变成了待观察状态,在上线后用户量大增的情况下就凸显出来。崩溃的慢慢累积,使得崩溃率在某个版本变成了很刺眼的数字。基于此情况,团队内部决定对当时的情况做彻底的治理、找到维护的方式方法。

京东金融App的崩溃治理持续了几个版本,崩溃数量前二十的问题基本修复完成。然而崩溃的修复并非是一帆风顺的,一些难以复现的问题经过修复观察再修复最终将问题解决。在修复原有问题期间App业务也在持续更新并带来了一些新的问题,对于新出现的问题研发团队格外关注,利用灰度发布阶段将问题消灭在萌芽之中。最终金融App将崩溃率稳定在万分之一以下。

根据2020年度移动行业性能体验报告,App行业平均崩溃在0.29%,Android端行业平均崩溃率在0.32%,而iOS端应用行业平均崩溃率在0.10%。

在这里插入图片描述
京东金融App经过高质量的持续修复,崩溃率远低于行业平均水平两个数量级,长期稳定在0.007%水平。

用户崩溃率数据来源于APM性能监控系统。
在这里插入图片描述
京东金融App的崩溃率远优于行业水平与研发团队的深入技术探索是分不开的,崩溃基础知识是技术探索的前提条件。本篇文章将由浅入深的讲解崩溃基本知识并分享典型崩溃案例的解决过程。

二、崩溃的定义

1、崩溃发生的原因

崩溃是CPU对发生异常的一种显式反应,CPU的异常处理是基于中断来完成的。中断是CPU暂停正在执行的程序,保留现场后转去执行相应的处理程序,处理完该事件后再返回断点继续执行被“打断”的程序。

在操作系统相关资料中介绍:中断(interrupt)和异常(exception)在不同的CPU架构里有不同的含义。

  • 比如在Intel架构中,中断处理的入口由操作系统内核中的中断分配表定义(interrupt dispatch table, IDT),IDT中有255个中断向量,其中前20个定义为异常(exception)的处理入口,即中断包含异常。
  • 而在ARM架构中,中断处理的入口则是在异常向量(exception vector)中,8个异常向量里边有3个是中断相关的,即异常包含中断。

不管如何界定中断和异常,CPU发生异常时,都会将控制权从异常前的程序交给异常处理程序,而且CPU将获得不会更低的执行权利,比如执行用户态的应用程序发生异常,CPU将切换到内核态,并执行对应的异常处理程序。经典的CPU五级流水线中一条指令的生命周期为[取指、译码、执行、访存、写回],每个阶段都可能出现CPU异常,比如在ARM架构下:

  • 在“执行”阶段产生的“数据中止”异常:若处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常。
  • 在“取指”阶段产生的”预取中止“异常:若处理器预取指令的地址不存在,或该地址不允许当前指令访问,存储器会向处理器发出中止信号,但当预取的指令被执行时,才会产生指令预取中止异常。

两种异常对应的处理程序会直接或者间接调用 Mach 内核的exception_triage() 函数,并将EXC_BAD_ACCESS作为入参传进去,exception_triage() 将会利用Mach消息传递机制投递异常。

2、在iOS系统中崩溃是如何发生的

在iOS系统内核(Mach)中,异常是通过内核中的基础设置「消息传递机制」处理的,异常并不比一条消息复杂,异常由出错的线程和任务通过msg_send()抛出,然后由一个处理程序通过msg_recv()捕捉。处理程序可以处理异常,也可以清除异常,还可以决定终止应用程序。

对于App来说,当App试图做一些不被允许的事情,比如CPU无法执行某些代码(访问无效内存、修改只读存储区等),或者触发了操作系统的某些策略(内存占用高、App启动时间过长等),操作系统将通过终止你的App来保护用户体验。

在一些开发语言中,一些编程对象遇到错误也会停止程序运行而发生崩溃。比如Object-C/Swift中越界访问数组,NSArray/Array会触发崩溃并停止程序运行。

二、常见崩溃的几种类型

1、野指针

野指针即指向一个不确定的内存地址,通过野指针访问这个内存地址时可能会发生各种不确定的情况。如果这块内存地址未被覆盖并不一定会出现问题,如果已经被覆盖或者分配成不可访问空间则程序直接崩溃。如果判断是野指针造成的崩溃,那么当前的崩溃代码很大可能并不是导致崩溃的原因,需要通过分析调用关系找到真正的崩溃原因。

在C语言中野指针经常发生在声明变量后未赋初值(随机地址)或指针释放后未置空,Object-C中的野指针多发生在多线程,当前线程访问的变量在另一个线程被释放。Object-C中指针默认值是nil,同C语言中的NULL,表示指针不指向任何内存空间。野指针错误结果通常都是变量或内存访问异常,常见的崩溃类型是EXC_BAD_ACCESS内存错误。

2、死锁

在iOS系统中,使用dispatch_sync在主线程执行同步任务就会产生死锁。如果因为某些复杂业务逻辑场景导致任务运行在主线程(下面示例代码),则会导致应用crash。

-(void)sceneAnalysis { 
  dispatch_sync(dispatch_get_main_queue(), ^{ 
    NSLog(@"Sync Task Result"); 
  }); 
  NSLog(@"Do Other Tasks"); 
}

程序会卡死在函数体的第一行,错误信息如下:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

以上示例代码是最典型的主线程死锁,主队列中添加了一个同步任务「NSLog(@“Sync Task Result”)」,所以主线程会暂停当前代码转去执行block代码块,并等待dispatch_sync函数返回后继续执行。但主

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值