APM从入门到放弃:可用性监控体系和优化手段的剖析

vtkq6444 8年前
   <p>在《黑客帝国》电影中较为经典的一幕是让Neo在红药丸和蓝药丸中做出选择。红药丸作为一个跟踪程序,帮助Neo定位物理身体位置,无论在哪里,出现任何问题都能够第一时间定位并解决。而开发者基本都知道,想解决大部分的功能性问题的难点基本就在定位上,而电影里面出现的一些人工智能、机器学习、虚拟现实的技术,也只能够在科幻电影中才能看到。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/191d697a9d0df183b5d2fb7a11812cb1.png"></p>    <p style="text-align: center;">季度活跃设备增长趋势</p>    <p>今天,在移动终端爆发以及用户需求的推动下,移动应用的“数量”和“体量”急速扩大,APP性能数据在优化产品上变得越来越重要,国内大批APM厂商仿佛一夜间遍地开花,整个监控体系也从服务端到APP端再到H5端不断的加强和改变策略来适应不同的场景需求,使得监控和优化的本质上已经发生了变化。</p>    <p><strong>APM的雏形发展</strong></p>    <p>在1996年时,Tivo与HP公司就从应用程序层面出发,他们认为网络无疑就是应用的速度。直至1998年,面向以组件为中心基础建设监控的APM产品出现,直到2011年,移动设备的普及和APP应用市场的爆发,让大家对移动端的性能体验要求也越来越苛刻。</p>    <p>在这个时候,国外的APM行业New Relic和AppDynamics已经在APM领域拔得头筹,国内一些APM厂商看准移动的这个趋势,APM仿佛一夜之间遍地开花,直至今日,作为国内比较具有代表性的APM厂商有:听云、OneAPM、云智慧、博睿等,当前BAT领域也跻身这一领域,阿里百川码力APM(简称“码力APM”)也在云栖大会中发布公测。开发者无需从零开始构建性能探针、数据平台和控制台,就可以通过可视化、可运维的方式长期监控应用性能、及时解决应用中存在的问题。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/f75aa73c504969e4ecb6a7a5257d7786.png"></p>    <p style="text-align: center;">▲ APM 业务与 IT 发展关系变迁</p>    <p><strong>APM可用性度量体系</strong></p>    <p>如今,国内APM业务竞争越来越激烈,大家纷纷在可用性、用户体验上发力。比如,大家用手机淘宝,明显感觉稳定性和流畅度比国内其他电商APP好很多,这不仅仅是因为他们有一堆优秀的开发工程师,更关键是其背后那一套完善的性能监控度量体系。</p>    <p>通过性能监控体系,app上发生的性能指标都会被实时上报,而码力APM服务端会基于这些指标进行聚类和分析,聚合出问题和性能瓶颈,同时完善的日志信息也将支持开发工程师及时修复和优化。</p>    <p>阿里技术专家陈武认为,在性能优化方面,以往的度量是通过APP的打开率来进行对比,很多都是非常主观。而度量体系里面面临的一个很大的问题是常态化。那么,应该如何建立起这一套可视化的性能度量的体系呢?</p>    <p>阿里百川将影响用户使用的性能指标分为可用性度量和体验度量。</p>    <p><strong>1、 可用性度量</strong></p>    <p>可用性包含app可用性和服务可用性。app可用性问题中最常见的就是crash,而用户遇到crash之后,大部分会选择直接卸载app;服务可用性问题则包含网络连接和服务端错误,这类问题往往可能造成用户购买、订阅等关键操作不可用,从而导致资损,而这类问题若长期未能解决,也会导致用户流失。</p>    <p>这类问题需要第一时间被修复,越早修复,止损的效果就越好。</p>    <p>这需要客户端探针具有强大的采集能力。探针SDK将负责采集用户由于线程异常、内存溢出、手机杀进程等各种原因导致的崩溃,并捕获到尽量全面的环境信息,和用户操作轨迹来帮助开发者还原用户操作,定位问题。同时,对网络请求部分也是同样,探针SDK需要支持自动采集网络性能指标,并捕获错误网络请求的日志,来辅助开发工程师解决问题。</p>    <p>但是探针在用户app端采集的均是单一的事件,若有1000个用户出现可用性问题,那么服务端接收到的可能就是1000份日志。让开发工程师在海量的日志中排查问题,显然可行性不高。这就需要APM服务端实时对这些日志进行语义分析以及高效的聚类,比如,将1000条用户日志聚合为3个问题,通过控制台反馈给开发者。这将大大提升开发工程师排查和解决问题的效率。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/f369f2c680f0292e4e7f745335730130.jpg"></p>    <p><strong>2、 APP体验度量</strong></p>    <p>APP体验是影响用户留存和活跃的关键,大家对APP使用过程中“如丝般顺滑”都具有天然的好感。但是目前市场大部分APP的体验依旧非常差,用户常会面对卡顿、图片加载失败、页面长时间等待等各种不良体验。这个时候,非常需要有一个系统体系化的去陈列和度量这些体验类问题。</p>    <p>APM控制台对卡顿的处理方式和崩溃类似,同类型的卡顿将被聚类在一起,发生该卡顿的用户详细日志也聚合在一起可以翻页查阅。而对图片加载失败等,页面元素无法正常显示的问题,则可以关注该图片所在静态资源的服务主机是否异常(单分钟请求量过多、图片过大等)。若该静态资源服务正常,则可以关注请求该图片的URL的错误率,可以反推是否为图片本身的问题。</p>    <p>在性能优化的量化方面,如何帮助企业去做定制?陈武认为,应该串联关键路径所需要的全部URL,从关键路径整体来看服务的健康度指标,而非关注全部的URL。比如通过网络性能监控,开发者无需对所有的URL进行关注,不同的开发者关注的核心业务不同,大家关注的URL也不一样。比如,在电商的场景,一个关键的路径是用户通过登录,打开商品,进入详情,然后下单到支付,通过把对应的关键路径所有的URL整合在一起,保障这条关键链路的性能,才能够强化核心业务的服务以及稳定性。</p>    <p><strong>APM的可用性检测方式</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e8420cde02b36080b21ee69181ae1ad8.png"></p>    <p style="text-align: center;">▲ 阿里百川码力APM的监控体系</p>    <p>对于加强应用的可用性,APM一般都采取应用监控结合服务监控的形式,使得开发者实现端到端的全链路性能管理。在码力APM监控体系中,阿里巴巴技术专家熊奇介绍了码力APM在监控体系里面的应用监控、服务监控、数据库以及消息推送等性能监控,主要通过以下方式来完成:</p>    <p>★ 在应用监控上,采集了iOS、Android应用的内存、CPU、崩溃、网络等方面的性能数据;</p>    <p>★ 在服务监控上,支持Tomcat、Jetty、JBoss容器和Spring、Struts等框架的性能检测;</p>    <p>★ 支持MySQL等SQL数据库和Redis、Mem cache等NoSQL数据库的性能检测;</p>    <p>★ 码力APM还提供了支持淘宝消息服务TMC、分布式框架Dubbo、淘宝API调用的性能检测。</p>    <p>对于数据采集之后会统一进入可以承载海量数据的存储系统和日志系统,统计系统会利用落地的数据完成数据的计算处理、生成报表,帮助开发者长期跟踪应用和服务的性能,而告警系统则会根据规则在问题发生时发出短信、邮件等即时告警,从而帮助开发者及时解决问题,降低损失。</p>    <p>可用性的度量检测方式-性能</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/1e0eec3187b7e7f1e3dcffb52d87001c.png"></p>    <p>在应用开发时,程序错误、主线程卡顿和资源使用超过系统限制导致的崩溃,是最严重、也是需要首先解决的问题。</p>    <p>通常开发者会借助模拟器、Instrument或者自动化测试发现一部分问题,但是测试往往难以覆盖用户使用场景下的设备、网络等环境。如果借助于社交媒体或者邮件反馈渠道,虽然可以有限地拿到真实的用户反馈,但是用户往往不能清楚的描述出复现问题所需的信息,往复沟通成本极高。所以,在客户端上,码力APM通过以下检测方式来收集应用崩溃信息。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4d1d5763e68a44a9224373c0fda80521.png"></p>    <p>码力APM在信号捕获方式中,通过sigaction设置信号中断时的回调,这样,就可以在回调中根据程序运行状态生成对应的崩溃日志。此外,对于SIGARBT(abnormal termination),我们还需要通过NSSetUncaughtExceptionHandler来获取未捕获异常的堆栈,来补全崩溃信息。</p>    <p>而后,把崩溃日志上报到码力APM,会依据崩溃日志的堆栈信息,聚合同一类型的崩溃后写入数据存储。同时,告警系统可以依据崩溃次数、崩溃率等规则,即时发出告警。</p>    <p>此外,码力Apm提供了dSYM上报脚本,在Xcode的build phrase中添加脚本,就可以在编译成功后自动上报dSYM文件。通过对dSYM文件的解析,重新聚合后写入数据存储,聚合可以减少高达90%数据库行数;同时,也实现了崩溃日志符号化。不依赖mac环境符号化,更好地利用云计算平台服务更多开发者。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/c4dd9833a1a0dded80954b6728fa2bd1.png"></p>    <p>第二种技术是卡顿检测,卡顿检测的基础是RunLoop,通过RunLoop Observer监听主线程RunLoop状态的变更。在这里,把RunLoop当作在操场上跑圈的运动员,把Before Sources当做每圈的起点,同时另外开启一条线程作为计时员,每5秒判断一次RunLoop是否跑过一圈。如果5秒内RunLoop没有完成一次RunLoop,则视为主线程卡顿。在发现主线程卡顿后,会生成卡顿日志,如果是复现的卡顿,可以选择不重复上报。</p>    <p>此外,针对设备不同的运行时期,如启动阶段、后台阶段、空闲阶段,我们会动态调整阈值,降低检测的开销。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/83577dd2c33593ba0bbb26f180b79486.png"></p>    <p>对于无法通过信号捕获、卡顿检测的崩溃,码力APM引入了应用中止检测,中止检测虽然不能还原崩溃现场,但是可以揭示问题的存在。在应用进入active状态时,码力APM在持久存储上设立一个标志位,表示程序在正常运行。在应用退出active状态或检测到崩溃时,码力APM就清除持久存储上的标志位,表示程序在已知的情况下退出。这样,在下一次应用启动时,如果持久存储上的标志位为真,则说明应用上一次运行在未知情况下退出,这种情况码力APM就计为应用非正常中止上报。</p>    <p>同时,为了过滤因为电量耗尽导致的关机,码力APM还增加了电量检测,在低电量时,清除标志位,避免中止误报。</p>    <p><strong>可用性的度量检测方式-网络</strong></p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/6808f665e7661201bccc61b6219cb73e.png"></p>    <p>请求错误、流量开销高、被运营商劫持等网络问题是应用开发时另一类棘手的问题。当然我们也可以借助模拟器、Instrument或者自动化测试发现简单的网络问题,但是测试难以覆盖复杂的用户网络环境,也难以导出网络性能数据进行长期比对监控。如果使用手工埋点的方式记录网络性能,一方面,我们需要应对多种系统网络接口,另一方面,我们需要同步应用网络代码和埋点代码,维护成本将会居高不下。</p>    <p>为了监控应用在真实网络环境中的性能,码力APM中引入了无痕埋点的网络性能监控,在网络检测中引入三种注入技术,帮助开发者长期监控应用的网络性能,优化产品用户体验。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e5c8f5c6b73033c208b54cde512bbd5c.png"></p>    <p>第一种是Method Swizzling。每一个NSObject类都包含一个isa指针,指向objc_class结构体,而每一个objc_class结构体又包含一个methodLists指针,指向objc_method_list结构体数组,在objc_method_list里又包含一个objc_method结构体成员,且每一个objc_method包含一个method_imp指针,指向方法实现。</p>    <p>因此,只要能修改method_imp的值,我们就能替换原有的实现。在<objc/runtime>中,通过class_getClassMethod和class_getInstanceMethod取得objc_method结构体指针,而后通过method_getImplementation取得方法的原始实现地址originIMP,之后在imp_implementationWithBlock生成新实现imp的参数block里,调用原始实现,就可以原有行为前后加入网络性能埋点行为。最后调用method_setImplementation替换方法实现。这样,任何调用都将使用新的实现。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/e912d94a433df9310267bd8781cc7e17.png"></p>    <p>第二种技术是Proxy。在Objective-C里,NSProxy是除NSObject外唯一的根类。NSProxy是一个实现了NSObject协议的抽象类,它的正常运作需要子类override -methodSignatureForSelector:方法为sel提供方法签名,以及-forwardInvocation:方法来完成调用的转发。</p>    <p>使用Proxy来注入NSURLConnection、NSURLSession等对delegate的回调。具体来说,在delegate proxy收到消息时,如果不是目标协议方法,则通过消息转发机制,转发给原delegate;如果是目标协议方法,则直接调用proxy实现,在proxy实现中委托调用原delegate;此外,多数协议和协议方法都是可选的,因此,在proxy的实现中需要实现-conformsToProtocol:和-respondsToSelector:方法来声明proxy额外加入的协议和方法。这样,我们就能在不影响原有回调的同时,增加网络性能埋点逻辑。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/b4ca54983c191516ebe966d2a491fde5.png"></p>    <p>第三种技术是fishhook。使用fishhook来替换动态链接库中的C函数实现,具体来说是CFNetwork和CoreFoundation中的相关函数。这里,以开车的模型来解释动态链接。设想一名新手司机开车从巴黎到罗马,因为他不知道路线,于是他先去咨询老司机;老司机告诉他正确的线路,这一次他可能还会绕点路,但下一次,他就会按照老司机的建议直接开到罗马。</p>    <p>相应的,在程序运行时,动态链接的C函数dynamic(…)地址记录在__DATA segment下的__la_symbol_ptr中;初始时,程序只知道dynamic函数的符号名而不知道函数的实现地址;首次调用时,程序通过__TEXT segment中的__stub_helper取得绑定信息,通过dyld_stub_binder来更新__la_symbol_ptr中的符号实现地址;这样,再次调用时,就可以通过__la_symbol_ptr直接找到dynamic函数的实现;如果我们需要替换dynamic函数的实现,只需要修改__la_symbol_ptr即可。具体的实现方式,可以参阅非死book的开源框架fishhook。</p>    <p><strong>加强可用性的优化手段</strong></p>    <p>通过以上两种检测方式,基本能够大部分的性能和网络需求,使得开发者能够满足如今移动互联网下用户的苛刻的需求,那么,建立起来的度量体系后,了解的具体的问题后,我们应该如何去解决这些问题来提升可用性呢?</p>    <p><strong>1、网络安全</strong></p>    <p>运营商、DNS被劫持问题是应用开发时一类棘手的问题, 解决方案也比较多。51信用卡技术总监汪睿认为,51信用卡作为金融属性的产品,基于安全考虑会放在第一位。解决方案主要是基于全栈HTTPS的方案来处理,但会带来一些成本和性能上的损耗。甚至可以像非死book、google等一些解决方案,使用HTTP2.0方式,这取决于公司和开发者自身去评估实现的成本。汪睿还介绍了早起的一个过渡方案,那就是HTTP的DNS方式,通过获取一个IP表通过IP来直接连接,可以避免HTTP劫持的问题。</p>    <p>而网络是一个端到端的技术,阿里高级技术专家陈武认为,从电商的场景看,首先要保证服务端的稳定性,服务端可以有反刷,限流,单元化,异地容灾,服务降级等策略保证连接的稳定性。另外,客户端的角度主要看连接链路和数据量。链路里面资源可以做多CDN的备份,通过HTTP DNS或者HTTPS,HTTP2.0来反劫持。在链路稳定的基础上,接着去保证传输的效率,这里面可以通过就近接入,连接复用,提升压缩率,使用二进制协议等技术来减少包大小。当然,这里面最重要的是端到端的网络监控体系,这样在网络服务治理上会更有抓手。</p>    <p><strong>2、系统降级</strong></p>    <p>降级的解决方案,是系统性能保障的最后一道防线,从性能优化的角度上说,没有100%完善的设计,总会有一些意料突发的情况导致性能恶化。所以,在系统设计时,必须做好降级设计。</p>    <p>饿了么移动首席架构师王朝成认为,在饿了么517大促活动上,服务器端承受非常大的压力,这个时候会通过降级部分服务的方式,来确保大促秒杀这种场景得以正常运行。但是,在用户端上,以及APP,还在不断积极的发送用户请求和数据,反而增加服务器集群的压力。这个时候,王朝成表示,他们会考虑把一部分的SDK或者APP上的服务也进行降级,来减少服务端在分析数据上的压力。</p>    <p>降级分为手动降级和智能降级,在策略上分为流量降级、效果降级、功能性降级。流量降级主要表现在通过主动拒绝处理部分流量早餐部分用户服务不可用。而效果降级和功能性降级都表现为服务质量的降级,一个是通过在流量高峰时期用相对低质量、低延时的服务来保障所有用户的服务可用性,另外一个是通过减少功能的方式来提高用户的服务可用性。</p>    <p><strong>3、网络性能</strong></p>    <p>从数据结构上,需要根据不同的业务场景来选择合适的数据结构,在数据流量较少的情况可能客户端上表现不出什么区别,当在数据流量过大,且数据结构复杂的时候很可能就是直接影响到APP的性能。</p>    <p>类似餐饮领域“饿了么”这样的应用,数据发送的频率使得据量会非常大,对用户来说可能没有什么感知,但是商家接收大量的订单,数据量影响很大,感知比较明显。王朝成认为,可以考虑一些新的协议(Protobuf, Flatbuf)来优化数据量,比如HTTP2.0可以压缩http协议的header,使用encoder来减少需要传输的header大小,通过通讯双方各自cache一份header fields表,对于相同的数据不再通过每次请求和响应发送,又减少了需要传输的大小。再一个是采取二进制的协议,只认0和1的组合,通过把原来http1.x的header和body部分用frame重新封装,实现方便且健壮。通过内容压缩与并发传输机制,在低速、不稳定的无线条件下,较少其http body的发送大小,改善用户体验和资源效率。</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/74bc5d18aa72d269498e0a389c7edc8e.png"></p>    <p style="text-align: center;">▲ http1.x和http2.0协议关系</p>    <p>同时,阿里高级技术专家陈武也表示,如果在链路没有问题的情况下,那么必须在整个网络传输层要尽量快,不然很容易出现timeout。所以,第一要从协议层,在协议层里面通过http2.0来减少包头的压缩,同时支持服务端push消息,且通过双通通道,对通道复用更快。第二是从数据层,数据可以通过二进制压缩。在整个网络连通率较低的时候,将打包拆成小包,达到很好的传输效果。</p>    <p><strong>4、动态热修复</strong></p>    <p>所谓热修复,就是使用热补丁动态修复技术,通过向用户发送Patch,在用户无感知的情况下完成一些致命bug的修复。51信用卡客户端负责人汪睿认为,在移动客户端上最大的一个问题是发版,对于iOS的用户来说,整个修复流程比较漫长。需要提交审核,但是在这段时间有可能已经错过很多用户。他认为,热修复技术能够很快并及时的在线进行修复,通常在使用的过程中就完成的修复过程。</p>    <p>在热修复技术上,Android常用的是基于Android dex分包方案,而iOS可以利用JSPatch,它可以使得你用JavaScript书写原生iOS APP,只需要在项目中引入极小的引擎,就可以用JavaScript调用任何的Objective-C的原生接口。</p>    <p><strong>总结</strong></p>    <p>以上所谈到的性能优化手段基本是为了解决三种情况所造成的问题:1. 日渐复杂的业务导致功能不断迭代所突发的致命bug修复方式,2. 日益增长的用户和膨胀的数据导致流量过大,3.网络安全和内存开销的问题。</p>    <p>本文通过不同的场景来分析移动性能优化的模式,可以通过确定场景下解决某一类型的问题。当然,我们不能仅仅通过了解性能优化所解决的问题以及手段,更重要的是需要清楚该问题所发生的场景、原因需要的成本。</p>    <p> </p>    <p>来自:http://www.androidchina.net/5917.html</p>    <p> </p>