InfoQ@补全分布式追踪的最后一块“短板”,在线代码级性能剖析

【InfoQ@补全分布式追踪的最后一块“短板”,在线代码级性能剖析】在本文中 , 我们详细介绍了代码级的性能剖析方法 , 以及我们在ApacheSkyWalking中的实践 。 希望能够帮助大家在线定位系统性能短板 , 缓解系统压力 。
分布式链路追踪的局限性在传统的监控系统中 , 我们如果想要得知系统中的业务是否正常 , 会采用进程监控、日志收集分析等方式来对系统进行监控 。 当机器或者服务出现问题时 , 则会触发告警及时通知负责人 。 通过这种方式 , 我们可以得知具体哪些服务出现了问题 。 但是这时我们并不能得知具体的错误原因出在了哪里 , 开发人员或者运维人员需要到日志系统里面查看错误日志 , 甚至需要到真实的业务服务器上查看执行情况来解决问题 。
如此一来 , 仅仅是发现问题的阶段 , 可能就会耗费相当长的时间;另外 , 发现问题但是并不能追溯到问题产生具体原因的情况 , 也常有发生 。 这样反反复复极其耗费时间和精力 , 为此我们便有了基于分布式追踪的APM系统 。
?通过将业务系统接入分布式追踪中 , 我们就像是给程序增加了一个放大镜功能 , 可以清晰看到真实业务请求的整体链路 , 包括请求时间、请求路径 , 甚至是操作数据库的语句都可以看得一清二楚 。 通过这种方式 , 我们结合告警便可以快速追踪到真实用户请求的完整链路信息 , 并且这些数据信息完全是持久化的 , 可以随时进行查询 , 复盘错误的原因 。
然而随着我们对服务监控理解的加深 , 我们发现事情并没有那么简单 。 在分布式链路追踪中我们有这样的两个流派:代码埋点和字节码增强 。 无论使用哪种方式 , 底层逻辑一定都逃不过面向切面这个基础逻辑 。 因为只有这样才可以做到大面积的使用 。 这也就决定了它只能做到框架级别和RPC粒度的监控 。 这时我们可能依旧会遇到程序执行缓慢或者响应时间不稳定等情况 , 但无法具体查询到原因 。 这时候 , 大家很自然的会考虑到增加埋点粒度 , 比如对所有的SpringBean方法、甚至主要的业务层方法都加上埋点 。 但是这种思路会遇到不小的挑战:
第一 , 增加埋点时系统开销大 , 埋点覆盖不够全面 。 通过这种方式我们确实可以做到具体业务场景具体分析 。 但随着业务不断迭代上线 , 弊端也很明显:大量的埋点无疑会加大系统资源的开销 , 造成CPU、内存使用率增加 , 更有可能拖慢整个链路的执行效率 。 虽然每个埋点消耗的性能很小 , 在微秒级别 , 但是因为数量的增加 , 甚至因为业务代码重用造成重复埋点或者循环使用 , 此时的性能开销已经无法忽略 。
第二 , 动态埋点作为一项埋点技术 , 和手动埋点的性能消耗上十分类似 , 只是减少的代码修改量 , 但是因为通用技术的特别 , 上一个挑战中提到的循环埋点和重复使用的场景甚至更为严重 。 比如选择所有方法或者特定包下的所有方法埋点 , 很可能造成系统性能彻底崩溃 。
第三 , 即使我们通过合理设计和埋点 , 解决了上述问题 , 但是JDK函数是广泛使用的 , 我们很难限制对JDKAPI的使用场景 。 对JDK过多方法、特别是非RPC方法的监控会造成系统的巨大延迟风险 。 而且有一些基础类型和底层工具类 , 是很难通过字节码进行增强的 。 当我们的SDK使用不当或者出现bug时 , 我们无法具体得知真实的错误原因 。
代码级性能剖析方法方法介绍基于以上问题 , 在系统性能监控方法上 , 我们提出了代码级性能剖析这种在线诊断方法 。 这种方法基于一个高级语言编程模型共性 , 即使再复杂的系统 , 再复杂的业务逻辑 , 都是基于线程去进行执行的 , 而且多数逻辑是在单个线程状态下执行的 。 ?
代码级性能剖析就是利用方法栈快照 , 并对方法执行情况进行分析和汇总 。 并结合有限的分布式追踪span上下文 , 对代码执行速度进行估算 。
性能剖析激活时 , 会对指定线程周期性的进行线程栈快照 , 并将所有的快照进行汇总分析 , 如果两个连续的快照含有同样的方法栈 , 则说明此栈中的方法大概率在这个时间间隔内都处于执行状态 。 从而 , 通过这种连续快照的时间间隔累加成为估算的方法执行时间 。 时间估算方法如下图所示: