一、开篇
公司从2021年开始在公司推广SkyWalking,在落地期间对开源SkyWalking也做了较多改造,为公司提供了全链路追踪能力,提升了大家排查问题、优化性能的效率。
1 规模
现有数据量已达数千亿
,数据规模如下:
- 每日存储
50亿
TraceSegment,约500亿
span链路数据 - 支持存储2月trace数据,共约
3000亿
TraceSegment,1.5万亿
span链路数据 - 链路数据每日
4TB-6TB
- 提供服务端、Native端、H5端的链路,有
500+Service
、7000+JVM实例
已接入
2 文章计划
拆分成系列文章,从Agent、OapServer(trace、metrics、topology等)、存储(ES)等方面介绍。
本文讲解SkyWalking的Agent。
我们是基于SkyWalking8.5.0版本升级改造,其他版本的架构、设计可能有所差异。
二、SkyWalking agent基本介绍
1 探针模型
Apache SkyWalking 的探针模型为分布式系统提供了自动、无侵入的追踪能力。
探针模型主要包含如下部分:
- Java Agent 探针:利用 Java Agent 技术,通过静态加载的方式,对目标应用的字节码进行增强。
- 插件化架构:微内核提供了基础的核心功能和通用服务,插件实现某些具体功能,如:某个特定框架或库的支持。
下面讲解一下Java Agent 探针和微内核 + 插件架构。
2 Java Agent 探针
2.1 Java Agent 探针和 SDK 方式探针
分布式追踪的探针,一般分为Java Agent 探针和 SDK 方式探针。
-
Java Agent 探针:利用 Java Agent 技术。例如 :SkyWalking、Pinpoint
-
SDK 方式探针:引入相应的 SDK 依赖。例如:Spring Cloud Sleuth、cat
关于Java Agent,可以参考笔者之前写的文章:【JVM】Java agent超详细知识梳理
2.2 Java Agent 探针的优势
SkyWalking的Java Agent 探针和常见的 SDK 方式探针对比,有如下优势:
- 无侵入性:使用 Java Agent 探针,开发者无需修改应用的源代码引入SDK,通过 JVM 参数在应用启动时加载 Agent,实现字节码增强来采集追踪数据,所以Java Agent 探针的接入、升级,都是更便捷。
- 自动追踪:SkyWalking agent 能够自动识别和追踪众多的流行框架和库,例如 HTTP 服务器、数据库调用和 RPC 框架,SkyWalking社区非常活跃,已提供了大部分主流的开源框架和库对应的链路插件。
- 智能选择与框架或库版本匹配的插件 :以 ElasticSearch client为例,SkyWalking Agent 提供了如
elasticsearch-5.x-plugin
、elasticsearch-6.x-plugin
和elasticsearch-7.x-plugin
等多个插件进行适配、链路追踪。对于 5.x 版本的 ElasticSearch 客户端,仅elasticsearch-5.x-plugin
会被激活,而其他版本的插件则保持不激活状态。而SDK方式探针,接入会麻烦一些,需要考虑引入的SDK依赖后,SDK版本是否能兼容现有的框架或库的版本。
3 插件化架构
SkyWalking Agent 采用了插件化架构(Plug-in Architecture)。
3.1 什么是插件化架构?
插件化架构也称为微内核架构(Microkernel Architecture),是一种面向功能进行拆分的可扩展性架构。
插件化架构中分为内核系统和插件模块:
-
内核系统:内核模块功能是比较稳定的,只负责管理插件的生命周期,不会因为系统功能的扩展而不断进行修改。
-
插件模块:功能上的扩展全部封装到插件之中,插件模块是独立存在的模块,包含特定的功能,能拓展核心系统的功能。通常,不同的插件模块之间互相独立,当然,你可以设计成一个插件依赖于另外一个插件,但应尽量让插件之间的相互依赖关系降低到最小,避免繁杂的依赖带来扩展性问题。
应用场景:
在基于产品的应用中通常会使用插件化架构,如工具类产品IDEA、Eclipse、Maven、Gradle ,众多功能都是以插件形式增加。
而日常用的开源框架、系统也是插件化架构:如Dubbo、ElasticSearch。
插件化架构的优点:
-
降低测试成本:从软件工程视角,插件化架构分离了变动部分与稳定部分,降低了测试的成本,符合设计模式中的开放封闭原则。
-
增强稳定性:每个插件都运行在相对独立的环境中,因此即便某个插件出现问题,也不会影响到核心系统和其他插件。
-
优良的可扩展性:新增功能或业务时,仅需添加相应的插件;而下线旧功能只需移除对应插件。
3.2 SkyWalking的插件化架构设计
SkyWalking的Java Agent 探针,以apm-agent-core模块为内核系统,apm-sdk-plugin、bootstrap-plugins等模块为插件模块。
对应的插件化架构设计如下:
内核系统
以apm-agent-core模块为内核系统,在这个内核中,定义了一系列核心的接口、类和功能,如:
- 服务注册:Agent 启动时,将自己代表的服务实例注册到 SkyWalking 后端,并定期发送心跳、上报元数据。
- 插件管理:负责加载、初始化和管理所有的 SkyWalking 插件。
- trace上下文管理:确保在请求的整个生命周期内,trace数据能在线程和进程间准确关联与传递。
- 数据上报:将采集的trace数据、metrics数据等发送到 SkyWalking 的后端服务。
- 配置管理: 解析和加载配置文件、环境变量,并支持从OapServer获取动态配置。
- 日志管理:提供日志记录的功能。
- 错误处理:确保在执行过程中遇到的任何错误都能被妥善处理,不会影响到目标应用的运行。
- 性能剖析:提供对应用程序的性能剖析功能,即 Profile,进行动态的性能剖析,收集方法级别的执行信息。
插件模块
-
apm-sdk-plugin : 功能:包含常见框架和库的自动探针插件。这是SkyWalking的核心插件库。 它包含了一系列专为各种流行框架和库(如Spring、JDBC和Elasticsearch)设计的插件。这些插件的主要功能是自动拦截和增强目标应用中的方法调用,从而实现对应用的无侵入式监控,捕获关键的跟踪数据。
子模块:dubbo-plugin、mysql-8.x-plugin等
-
bootstrap-plugins:
功能:捕获JVM启动早期行为的插件。 这些插件被设计用于Java Agent的bootstrap类加载器级别。其主要目的是拦截和增强Java的核心库,例如JDK自带的某些类。这种特殊的设计确保了在关键的、核心的场景中,跟踪可以被正确地执行,而不受应用自身或其他库的干扰。
子模块:jdk-threading-plugin、jdk-threadpool-plugin等
-
apm-toolkit-activation:
功能 :激活SkyWalking APM Toolkit库中提供的API。 SkyWalking APM Toolkit提供了一套API,允许开发者进行手动埋点,以满足某些自定义的监控需求。
apm-toolkit-activation
模块就是用来激活这些手动埋点功能的插件集。子模块:apm-toolkit-log4j-1.x-activation、apm-toolkit-trace-activation等
-
optional-plugins:
功能:可选插件,为特定场景提供额外功能。 它们不会在默认配置中被启用,但为特定场景或不常见的框架提供了额外的支持。用户可以根据实际需求选择性地启用这些插件。
子模块::gateway-3.x-plugin、trace-ignore-plugin、zookeeper-3.4.x-plugin等
-
optional-reporter-plugins:
功能:可选的数据上报方式插件。 SkyWalking Agent不仅支持默认的gRPC方式向后端上报数据,还可以通过这些可选的上报插件选择其他传输协议或格式。
子模块:optional-reporter-plugins等
三、agent改造-缩减agent编译、启动时间
1 缩减agent编译时间
agent编译打包是非常耗费时间,本地编译打包往往5到10分钟才能完成,且agent代码有改动必须完整编译打包再挂载才能调试,所以频繁执行漫长耗时的编译打包,对开发调试带来很大的不便。
于是,我们做了下面的这些优化,预估减少了**50%**的编译时间。
1.1 去除用不到的插件
在maven管理中去除用不到的agent插件,例如:baidu-brpc-plugin
、cassandra-java-driver-3.x-plugin
等十几个插件,代码量减少,加快编译速度。
1.2 使用maven并行编译
maven支持指定多线程并行编译,对应的参数是-T
,对应使用方式如下:
mvn -T 4
表示用 4 个线程构建。mvn -T 2C
表示每个CPU核分配 12个线程进行构建,8核则为16线程。
效果:不同机器配置,调整不同线程数。使用
mvn -T
命令,预计减少30%-50%的编译时间。
2 缩减agent启动时间
SkyWalking Agent作为Java Agent挂载到宿主应用JVM,agent自身启动时间往往需要20秒 以上,如果应用有大量实例要部署,如某个应用有100个实例,则至少增加了2000 秒的启动耗时,严重影响发布效率。
于是我们做了如下技术改造,优化启动耗时。
2.1 去除Kafka topic 的检查部分
我们使用了Kafka通道上报agent数据,可以去除下面的Kafka检查topic是否存在的逻辑,因为Kafka的topic会提前创建好,没有必要做检查。
效果:启动耗时减少了3-4秒。
2.2 过滤不需要增强的类
SkyWalking Agent通过Byte Buddy 增强匹配环节,会扫描匹配较多的类。SkyWalking Agent自身也会过滤一些不需要的类,其过滤逻辑在org.apache.skywalking.apm.agent.SkyWalkingAgent#premain
方法的如下代码:
java
AgentBuilder agentBuilder = newAgentBuilder().ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.groovy."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameContains(".reflectasm."))
.or(nameStartsWith("sun.reflect"))
.or(allSkyWalkingAgentExcludeToolkit())
.or(ElementMatchers.isSynthetic()));
但是,SkyWalking过滤的类很有限,而一般应用自身的类有几百个以上,此外也会引入几十个JAR,这些类累计起来至少有几千个类。
而我们实际需要的插件总数不过几十个,需要增强处理的类其实也可能就几十、上百个。所以我们扩大过滤的范围,只匹配需要增强的类:
-
移除未使用到的插件,避免不必要的匹配逻辑
-
com.alibaba
开头类,只增强dubbo相关类MonitorFilter
、druid相关类com.alibaba.druid.**
org.apache
开头的类很多,只增强名字是xxx的几个类- 对于继承型匹配,尽量约束开发者实现类的全限定名的根。特别是当这个类是一个接口或抽象类,并且它有很多实现类或子类时,插件开发者应当尽量精确地指定这个类的全限定名。这样,SkyWalking只会增强那些真正需要的类,避免不必要的性能开销
- 自身公司的package如果命名规则为
com.company.xxx
,且有自己构建trace的需求,则com.company.xxx
不忽略
如何确定哪些类需要增强?
上面提到控制匹配逻辑,限定只匹配的需要增强的类。那如何确定哪些类需要增强?有下面的方式:
1.找到需要用到的插件,找到xxxInstrumentation
插桩类,搜索需要增强的类,例如druid-1.x-plugin
插件中,插桩类是org.apache.skywalking.apm.plugin.druid.v1.define.DruidDataSourceInstrumentation
,对应的代码:
java
public class DruidDataSourceInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "com.alibaba.druid.pool.DruidDataSource";
}
其中com.alibaba.druid.pool.DruidDataSource
就是要增强的类。
2.将agent日志级别调整到debug,
properties
logging.level=${SW_LOGGING_LEVEL:DEBUG}
应用接入agent后,运行一段时间,当agent采集了项目里框架、组件的链路后,再查找skywalking-api.log,搜索关键词enhance class {} by {} completely.
,下面是一段日志示例:
bash
DEBUG 2023-10-08 19:23:42.791 main AbstractClassEnhancePluginDefine : enhance class org.apache.catalina.core.StandardHostValve by org.apache.skywalking.apm.plugin.tomcat78x.define.TomcatInstrumentation completely.
其中StandardHostValve
是要增强的类,ControllerInstrumentation
是插桩类。
四、小结
本篇先讲解Agent的探针模型,以及缩减agent编译、启动时间的升级改造。
由于篇幅原因,其他Agent改造部分:
-
dubbo参数采集
-
灰度发布、快速卸载
-
agent改造-自研插件
-
其他优化
这些内容将放到下一篇文章。
参考
前同事硕总写的文章:
笔者文章: