背景
Cloud Native
在 2018 年的 2 月,ARMS Java Agent 的第一个版本正式发布,为用户提供无侵入的的可观测数据采集服务。6 年后的今天,随着软件技术的迅猛发展、业务场景的逐渐丰富、用户规模的快速增长,我们逐渐发现过去的功能以及架构的设计逐渐难以合理、优雅的满足今天的需求,重构越来越多的被提及,但总是缺少一个合理的契机。
适时,OTel 项目异军突起,其社区经过短短四年的发展,活跃度位列 CNCF 第二,逐渐成为可观测领域的开源标准。OTel(OpenTelemetry)是一个位于云原生计算基金会(CNCF)的开放源代码项目,旨在标准化遥测数据的收集、处理和导出的方式。其贡献者由来自不同公司和组织的成员组成,他们共同协作创建和维护用于分布式追踪、指标和日志的 API、SDK 和工具。他们的目标是使可观测性更加易于访问并整合到云原生软件开发中,从而使用户能够更有效地监控、调试和优化他们的应用程序。
这些现象也极大的引起了我们的兴趣, 促使我们对 OTel Java Agent 进行了一次深入的调研。
关于 OTel Java Agent
Cloud Native
首先我们对比了 OTel Java Agent 和 ARMS 现有探针在 Tracing、Metrics、Logs、Profiling 以及其他五个方面的功能差异,如下表所示。可以看到,简单的从功能层面来说,OTel Java Agent 依托社区广大的贡献者,在插件数量上远远领先。此外,基于一些领先的埋点技术,对于各种异步场景支持较好;ARMS 探针则依托广大的商业化用户场景和多年服务集团内外客户的经验,在采样、多协议支持、指标丰富度以及各种三方功能集成方面比较领先。
除了上面列举的功能对比,在对 OTel Java Agent 调研中,我们也发现他有很多领先的设计在解决埋点生效判断、异步、类隔离等问题时十分方便。这里简单介绍一下他的几个比较领先的设计。
JVM 的字节码增强机制有一些限制,对于已加载类的增强,只能修改方法体,不能给类新增字段。这个限制对于我们影响较大,因为在 APM 的场景下,往往有较多的场景需要给类增加字段来作一些变量传递。opentelemetry-java-instrumentation 提供了 VirtualField 机制,如下图所示,通过统一的编程接口,可以给类 T 添加一个类型为 F 的字段。
当类 T 当前尚未加载时,此时的实现就是给类 T 增加了类型为 F 的字段。当访问类 T 的 F 字段时和访问普通的类字段一致。 当类 T 已经加载时,此时的实现是有一个全局的 ConCurrentWeakhashMap,map 的 key 类型为 T,value 类型为 F。当访问类 T 的 F 字段时实际在底层为 map 的 get 操作。
public static <U extends T, V extends F, T, F>
VirtualField<U, V> find(Class<T> type, Class<F>fieldType) {
return RuntimeVirtualFieldSupplier.get().find(type, fieldType);
}
除了原生的 JDK 线程池,对市面上常见的异步框架 akka、netty event loop 等均做了异步埋点。异步埋点思路整体上包含两个步骤:
实现了 Runnable 接口的实现类,利用上述的 VirtualField 机制,给实现类增加一个记录 Trace 上下文的字段,同时埋点其 run 方法,run 方法执行时获取增加字段中的 Trace 上下文,并设置到当前线程的 ThreadLocal 中。
埋点 Exectuor 的 execute 方法,在 execute 方法执行时从 ThreadLocal 获取当前 Trace 上下文,并设置给对应的 Runnable 实现类。
最大程度利用框架的拓展能力进行埋点,比如利用 Dubbo 的 Filter 机制、grpc 的 Intercepter 机制、实现 lettuce 的 Tracing 接口等等。而不是一味的对框架的方法进行增强。
调研结论
Cloud Native
围绕 OTel Java Agent 做了哪些增强
Cloud Native
围绕 OTel Java Agent 做了哪些增强
Cloud Native
Tracing 能力是 APM 探针的核心能力,OTel 探针原生的 Tracing 能力在企业内部复杂场景下往往会遇到不少挑战,包括多协议场景下断链、极端场景下 Span 数量爆炸、采样难以命中高价值数据等等。针对这些问题,我们对 OTel 探针做了以下增强来解决:
多协议支持
优点:在客户多语言、内部不同部门使用多套 Tracing 系统、外部流量携带 Trace 上下文、上云迁移等场景下能尽量保证不断链。
调用链压缩
原理:ARMS 探针会将一些同一层级的重复 Span 压缩成一个,比如业务代码在一个 for 循环中,调用数据库应用 10000 次,那么在调用链中会生成 10000 个 Span,而经过调用链压缩后,仅会记录一个 Span,并在这个 Span 中记录重复 Span 的次数、最大耗时、最小耗时、总耗时等信息,效果如下图所示。
缺点:因为仅保留了部分样本,无法看到全部的信息,可能导致丢失用户真正关注的数据。
采样
相比于其他产品单一的采样策略,ARMS 探针提供相对较为丰富的采样策略,且大多不需要用户进行复杂配置,每个采样策略保证特定场景下高价值 Tracing 数据被采样,低价值 Tracing 数据少采样,分别如下所示:
固定比例采样
即现有的默认采样,按照百分比采样链路。
自适应采样
自适应采样会按照 LFU 的策略选取当前调用量 top-1000 的接口,每个接口的采样彼此隔离,可设置两种采样策略,两种策略两种采样分别如下所示。
每秒固定条数(默认):一秒采样 10 条。 自适应比例:默认 10%,会根据该接口上一分钟请求量动态调整,避免大流量接口采样太多无效数据。
另外对于调用量 top-1000 以外的接口,可以认为是一个 other 接口。处理逻辑和前面介绍的 top-1000 中任 1 接口一致。
小流量采样
无需用户配置,自动保证每一个接口每一分钟至少有一个 Span。原理是用一个布隆过滤器存储一分钟内已经被采样过的接口。并每一分钟定期重置该布隆过滤器。这样可以保证无论用户接口有多发散,内存开销都是确定的。
错慢异常采样
无需用户配置,当一次调用满足下面三个条件时,则上报该次调用相关 Span。
接口报错:http 类接口响应码非 2xx、3xx 或者本次调用的 localRootSpan 埋点方法处抛出异常。 接口内部有异常:一次调用的非 LocalRootSpan 的 Span 记录到异常信息。 接口调用耗时长的定义:接口耗时大于过去一段时间该接口的 p99 耗时。
该采样对于问题排查十分重要,但是因为时机问题,无法保证链路完整。比如接口 A 调用接口 B,A 命中错慢采样,并不能保证 B 接口的 Span 一定上报。
自定义采样
即用户自己配置 100% 采样接口、接口前缀、接口后缀等等。满足用户配置要求的调用会一定采样。
总结
上述各个采样策略会在一次调用中都生效,一个 Span 是否上报由上述采样策略综合决定,详细流程如下图所示:
其中不同颜色的采样策略区别在于:
紫色:标准的头采样,只会在链路的 RootSpan 处触发,采样后可以保证后续链路完整。 蓝色:只要当前的采样结果是不采样,可以在链路的任何一个 LocalRootSpan 处触发,采样后可以保证后续链路完整。 绿色:只要当前的采样结果不采样,可以在链路的任何节点触发,采样后无法保证后续链路完整。
以一个常见的链路 A->B->C 为例说明,在不同节点命中不同采样规则时,对应会链路哪些 Span 会上报,哪些 Span 不上报。
更丰富的指标
1)线程池监控指标:针对常见 JDK 线程池,Jetty、Undertow 线程池监控,支持核心线程、最大线程、活跃线程、当前线程、历史最大线程、调度任务、完成任务、拒绝任务以及队列大小 9 类指标。便于排查线程池打满类问题。
2)线程监控指标:将当前 JVM 种所有线程归类后,统计不同类别线程的耗时以及处于不同状态线程的数量,并定时抓取线程栈,便于排查线程阻塞,线程耗时高等问题。
3)MQ 消费延迟指标:针对 MQ 类组件,出了常见的 RED 指标,增加消费延迟指标,便于排查消费延迟类问题。
4)数据库响应大小:针对 DB 类操作,增加请求、响应大小指标,便于排查大查询类问题。
5)新增异常类指标:指标主要为异常次数,维度记录了当前接口,便于排错异常类问题。
更多样的维度
1)接口类 RED 指标记录上游接口:方便查看接口到接口的调用拓扑、调用关系,方便接口异常时快速定位上下游。
2)数据库类调用指标。额外记录当前接口、数据库语句维度,方便接口出问题时,快速定位是否 db 的问题。db 出问题时,快速查询影响的接口,具体出问题的 sql 语句。
和阿里云 Dragonwell 团队合作,底层基于 async-profiler,提供 CP(Continuous Profiling)的能力。阿里云 Java Agent 提供的 CP 支持多种剖析类型,比如 CPU 热点剖析、堆内存热点剖析,墙钟热点剖析等。
除了常见的 CPU 热点剖析、内存热点剖析,ARMS 还针对慢调用链诊断场景,提供了代码热点产品能力,其是在开源 Async Profiler 墙钟能力的基础上,通过关联调用链中的 TraceId & SpanId 信息提供了调用链级别的 On & Off-CPU 火焰图,可有效对 Tracing 的监控盲区细节进行还原,帮助用户诊断各类常见的慢调用链问题,详情可参见文档[1]。
那么,和客户自己用开源的 Async Profiler 生成火焰图相比有什么优势呢?
首先,支持常态化开启。开源的 Async Profiler 未提供支持常态化开启的数据存储与处理能力,难以在生产环境常态化开启,对于一些线上偶现的问题,难以使用其进行问题排查。
其次,运行环境覆盖面更广。开源的 Async Profiler 一些剖析类型对应用运行环境有一定要求,比如 Alpine Linux 基础镜像为了控制体积而去除了 JDK 调试符号(debug symbols)导致无法使用内存热点剖析功能,但是 ARMS 在其基础上通过针对特定版本的 Alpine Linux 基础镜像对应的 JDK 调试符内容做了预适配,对相关类型的环境,在不安装调试符的情况下,也可以使用内存热点。
集成代码级问题诊断利器 Arthas。无需依赖 JDK,一键开启、关闭。常见命令白屏化操作。且支持企业级鉴权、审计能力。避免任意用户随意执行 Arthas 命令。详情见 Arthas 诊断[3]。
内存 Dump。一键对指定机器执行内存 Dump,并配套白屏化分析能力。
微服务治理。在同一个 Java Agent 中集成了阿里云 MSE 微服务治理能力,包括全链路灰度、限流降级、无损上下线,系统防护、消息灰度等。 集成云安全中心应用安全 RASP。一键开启后拥有危险组件检测、25+ 种攻击行为[4]的监控,阻断的能力。
由于 Java 探针和用户代码运行在一个进程中,且会对用户代码进行增强修改,Java 探针的稳定性建设尤为重要,多年的公有云用户服务经验告诉我们,对于一款可观测产品而言,我们的底线是不能影响业务行为,比如导致用户进程启动失败,用户进程 crash,用户接口报错,占用大量用户机器资源等等。为了最大程度的避免这类问题,并在出现这类问题时能够及时止血,我们在 OT 的基础上增加了下述能力。
探针 CPU/内存占用上限控制能力:在探针 CPU 开销,内存占用超过指定阈值时,自动降级探针的 Tracing 或者 Metrics 数据采集能力。 探针启动预检能力;因为探针本身有运行的环境要求,为了避免在非预期环境中出现异常行为,探针启动有若干检测项,比如 JVM 类型、JVM 版本、最大堆内存等等,最大程度的避免影响用户业务。 探针功能可动态插拔能力:大部分探针功能,特别是可能影响用户业务的能力,都具备动态控制开关,可以在出现问题时快速关闭。
阿里云 Java Agent 4.0 探针带来了哪些好处
Cloud Native
从功能层面上来说,这次升级,完全吸纳了 OTel 的优秀设计,对我们现有的很多功能做了升级或者增强。
遵循 JDBC 规范的数据库埋点从 JDBC 接口层面埋点,理论上支持所有遵循 JDBC 规范的数据库埋点(3.x 探针仅支持固定的 9 种)。 异步埋点无需用户配置,不会断链。 vertx、webflux、lettuce、Rabbitmq、kafka、RocketMq、ONS 等插件相比老版本,因为埋点的位置优化,指标统计更准确,支持版本范围更广。 支持容器场景的系统指标采集。 线程池监控支持用户自定义线程池的监控。
从工程质量上来看来说,这次升级重构是对 3.x 代码的一次取其精华、去其糟粕的过程、是重新树立更合理科学的开发规范的过程,通过这次升级重构:4.0 探针的内存占用下降了 20%、线程数降低了 60%,探针包大小降低了 30%。
我们为社区做了什么
Cloud Native
在基于 OTel Java Agent 二次开发的过程中,我们也积极的反哺开源,在过去 6 个月中,我们累计向社区贡献并合并各类 PR 40+,其中包含新增在国内广泛使用的 XXL-JOB、InfluxDB、MyBatis 等插件,参与社区日常 PR Review 100+,steverao[5]和 123liuziming[6]两位同事成为社区 member,其中 steverao 受邀作为该项目的 Triager 并且负责该社区的日常维护、代码 CR,且个人贡献长期位列社区前四名,贡献度积分位列社区 Top 20,亚太地区第一。
此外,我们也积极参与社区相关各类会议活动,今年 6 月,受社区邀请,在北美举行的 2024 OpenTelemetry Community Day 活动中,我们同事望陶和铖朴,为社区带来了《GraalVM 静态编译下 OTel Java Agent 的自动增强方案与实现》[7]主题分享,对相关问题的原创性解决方案得到了社区开发者的广泛关注。今年 8 月,在中国香港举行的 KubeCon China 2024 大会上,望陶和铖朴与社区其他开发者一起,在社区 Governance Committee 团队的支持下,代表社区在大会上做了《社区最新进展以及阿里云拥抱 OTel 社区实践》相关分享。
此外,为了促进亚太地区与社区的交流,在团队相关同学向社区提议,在与社区 Governance Committee Member 成员沟通后,OTel 社区也在多个领域,设立了亚太地区友好的周会交流时间。其中包含 Java: SDK + Instrumentation、Semantic Conventions: LLM、Contributor Experience 和 Developer Experience,相关时间可以参考社区周会安排[8],相关会议中有社区最资深的开发者一起参与,欢迎有兴趣的朋友加入。
写在最后
Cloud Native
接下来的时间,一方面我们将持续的 Follow OTel Java Agent 的发版节奏,定期合并开源稳定代码,保障用户可以持续的享受社区最新的 feature;另一方面,我们也将重点打造阿里云 Java Agent 相比 OTel Java Agent 的差异化能力,补齐其不足与短板,帮助用户获得更全面、更透彻的应用可观测体验。
对 ARMS 有任何疑问,可搜索钉钉群号:31123480 加入 ARMS 沟通交流群。
相关链接:
https://help.aliyun.com/zh/arms/application-monitoring/user-guide/arthas-diagnosis
[5] steverao
[6] 123liuziming
https://github.com/open-telemetry/community?tab=readme-ov-file#special-interest-groups
[9] Go Instrumentation
[10] #1961
https://help.aliyun.com/zh/arms/application-monitoring/user-guide/versions-of-arms-agent-for-java
文章转载自阿里巴巴中间件。点击这里阅读原文了解更多。
CNCF概况(幻灯片)
扫描二维码联系我们!
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。请关注CNCF微信公众号。