™技术博客

skywalking | Agent启动原理

2021年2月17日

微内核架构 和 SPI机制

微内核架构

  • 微内核架构(Microkernel Architecture) 也称插件化架构(Plug-in Architecture),是一种面向功能进行拆分的可扩展性架构

微内核架构图:核心系统和插件模块

  • 内核系统:负责管理插件的生命周期,保证系统不因功能扩展而不断进行修改。
  • 插件模块:独立存在的模块,包括特定的功能,用于扩展核心系统的功能。相互模块之间应保持最小依赖原则,避免过分依赖带来扩展性问题。

    SPI机制

  • 服务的提供者提供了接口的实现之后,在Classpath的META-INF/services/目录里创建以服务接口命名的文件,此文件记录了该jar包提供的服务接口的具体实现类。
  • 当某个应用引入了该jar包且需要使用该服务时,SPI机制通过查找这个jar包的META-INF/services/中配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。
  • JDK SPI加载步骤
  1. JDK SPI的入口方法是ServiceLoader.load()方法
  2. LazyIterator.hasNextService()方法,主要负责查找META-INF/services目录下的SPI配置文件,并进行遍历
  3. LazyIterator.nextService()方法,负责实例化hasNextService()方法读取到的实现类
  • Dubbo SPI 改进
    • JDK SPI在查找具体实现类的过程中,需要遍历SPI配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。如果SPI配置文件中定义了多个实现类,而我们只需要其中一个实现类时,就会生成不必要的对象。
    • Dubbo将SPI配置文件改成了KV格式;其中key就是一个简单的标记,当我们为一个接口查找具体实现类时,可以指定key来选择具体实现。
    • ExtensionLoader.getExtensionLoader()方法会根据接口类型从缓存中查找相应的 ExtensionLoader实现。
    • 查找到接口对应的ExtensionLoader对象之后,会调用getExtension()方法,根据传入的key查找相应实现类,最终将其实例化后返回。

SkywalkingAgent启动流程

  • 入口是apm-agent模块中的SkyWalkingAgent类的premain()方法,完成了Agent启动流程
  1. 初始化配置信息: 该步骤中会加载agent.config配置文件,其中会检测Java Agent参数以及环境变量是否覆盖了相应配置项。
  2. 查找并解析skywalking-plugin.def插件文件。
  3. AgentClassLoader加载插件
  4. PluginFinder对插件进行分类管理。
  5. 使用Byte Buddy库创建AgentBuilder。根据已加载的插件动态增强目标类,插入埋点逻辑。
  6. 使用JDK SPI加载并启动BootService服务
  7. 添加一个JVM钩子,在JVM退出时关闭所有BootService服务。

配置读取及处理

  • SnifferConfigInitializer.initializeCoreConfig()方法中会将配置信息填充到Config静态字段中
  1. 按照${配置项名称:默认值}的格式读取各个配置项。
  2. 解析系统环境变量值,覆盖Config中相应的静态字段。
  3. 解析Java Agent的参数,覆盖Config中相应的静态字段。
  4. agent.config文件中全部配置信息填充到Config中相应的静态字段中。
  5. 检测SERVICE_NAMEBACKEND_SERVICE两个配置项,若为空则抛异常
  6. 更新初始化标记IS_INIT_COMPLETED

注1: loadConfig()方法会优先根据环境变量(skywalking_config)指定的agent.config文件路径加载。若环境变量未指定skywalking_config配置,则到 skywalking-agent.jar同级的config目录下查找agent.confg配置文件。
注2:将agent.config文件中的配置信息加载到Properties对象之后,将使用PropertyPlaceholderHelper对配置信息进行解析,将当前的“${配置项名称:默认值}”格式的配置值,替换成其中的默认值
注3:overrideConfigBySystemProp()方法会遍历环境变量(即System.getProperties()集合),如果环境变量以"skywalking."开头,则认为是SkyWalking的配置,覆盖agent.config中的默认值
注4:overrideConfigByAgentOptions()方法解析Java Agent的参数,覆盖agent.config中的默认值

插件启动流程

  • 加载插件时使用到一个自定义的ClassLoaderAgentClassLoader
  • 使用自定义类加载器,目的是不在应用的Classpath中引入SkyWalking的插件jar包,让应用无依赖、无感知插件。
  1. AgentClassLoader的构造方法中初始化classpath字段,该字段指向AgentClassLoader要扫描的目录(skywalking-agent.jar包同级别plugins目录和activations目录)
  2. 从Classpath下加载类(或资源文件);对应findClass()方法和findResource()方法。
  3. findClass()方法会扫描所有jar包,查找类文件
  4. findResource()方法会遍历allJars集合缓存的全部jar包,从中查找指定的资源文件并返回
  5. AgentClassLoader中有一个DEFAULT_LOADER静态字段,记录了默认的AgentClassLoader

插件解析器

  • 每个Agent插件中都会定义一个skywalking-plugin.def文件
  • PluginResourcesResolver是Agent插件的资源解析器,会通过AgentClassLoader中的findResource()方法读取所有Agent插件中的skywalking-plugin.def文件

Agent增强类

  1. 拿到全部插件的skywalking-plugin.def文件之后,PluginCfg会逐行进行解析,转换成PluginDefine对象;PluginCfg 是通过枚举实现的、单例的工具类
  2. 遍历全部PluginDefine对象,通过反射将其中defineClass字段中记录的插件类实例化;使用类加载器是默认的AgentClassLoader实例
  • AbstractClassEnhancePluginDefine抽象类是所有Agent插件类的顶级父类,定义了四个核心方法,决定了一个插件类应该增强哪些目标类、应该如何增强、具体插入哪些逻辑。
    • enhanceClass()方法:返回的ClassMatch,用于匹配当前插件要增强的目标类。
    • define()方法:插件类增强逻辑的入口,底层会调用enhance()方法和witnessClass()方法。
    • enhance() 方法:真正执行增强逻辑的地方。
    • witnessClass()方法:一个开源组件可能有多个版本,插件会通过该方法识别组件的不同版本,防止对不兼容的版本进行增强。

增强目标匹配类

  • enhanceClass()方法决定了一个插件类要增强的目标类,返回值为ClassMatch类型对象。ClassMatch类似于一个过滤器,可以通过多种方式匹配到目标类
    • NameMatch:根据其className字段(String 类型)匹配目标类的名称。
    • IndirectMatch:子接口中定义了两个方法。
    • MultiClassNameMatch:其中会指定一个matchClassNames集合,该集合内的类即为目标类。
    • ClassAnnotationMatch:根据标注在类上的注解匹配目标类。
    • MethodAnnotationMatch:根据标注在方法上的注解匹配目标类。
    • HierarchyMatch:根据父类或是接口匹配目标类。

插件查找器

  • PluginFinderAbstractClassEnhancePluginDefine查找器,可以根据给定的类查找用于增强的 AbstractClassEnhancePluginDefine集合。
  • PluginFinder的构造函数中会遍历已经实例化的AbstractClassEnhancePluginDefine,并根据enhanceClass()方法返回的ClassMatcher类型进行分类
    • 如果返回值为NameMatch类型,则相应AbstractClassEnhancePluginDefine对象会记录到该集合nameMatchDefine
    • 如果是其他类型返回值,则相应AbstractClassEnhancePluginDefine对象会记录到该集合signatureMatchDefine
  • find()方法是PluginFinder对外暴露的查询方法,其中会先后遍历nameMatchDefine集合和signatureMatchDefine集合,通过ClassMatch.isMatch()方法确定所有的匹配插件

Agent构造器

  • Byte Buddy如何使用加载到的插件类增强目标方法
  1. 首先会创建ByteBuddy对象:Config.Agent.IS_OPEN_DEBUGGING_CLASS 如果将其配置为 true,则会将动态生成的类输出到 debugging 目录中。
  2. 接下来创建 AgentBuilder 对象,AgentBuilderByte Buddy 库专门用来支持 Java Agent 的一个 API,相关方法
    • ignore()方法:忽略指定包中的类,对这些类不会进行拦截增强。
    • type()方法:在类加载时根据传入的ElementMatcher进行拦截,拦截到的目标类将会被 transform()方法中指定的Transformer进行增强。
    • transform()方法:这里指定的Transformer会对前面拦截到的类进行增强。
    • with()方法:添加一个Listener用来监听``AgentBuilder`触发的事件。
  3. PluginFInder.buildMatch()方法返回的ElementMatcher 对象会将全部插件的匹配规则(即插件的enhanceClass()方法返回的 ClassMatch)用OR的方式连接起来,这样,所有插件能匹配到的所有类都会交给Transformer处理。
  4. with() 方法中添加的监听器——SkywalkingAgent.Listener,它继承了 AgentBuilder.Listener接口,当监听到Transformation事件时,会根据 IS_OPEN_DEBUGGING_CLASS配置决定是否将增强之后的类持久化成class文件保存到指定的log目录中; 注意,该操作是需要加锁的,会影响系统的性能,一般只在测试环境中开启,在生产环境中不会开启。
  5. Skywalking.Transformer,它实现了AgentBuilder.Transformer接口,其transform()方法是插件增强目标类的入口。Skywalking.Transformer会通过PluginFinder查找目标类匹配的插件(即AbstractClassEnhancePluginDefine对象),然后交由AbstractClassEnhancePluginDefine完成增强

    注意:如果一个类被多个插件匹配会被增强多次,当你打开IS_OPEN_DEBUGGING_CLASS配置项时,会看到对应的多个class文件。

服务管理

  • JDK SPI技术加载BootService接口的所有实现类,BootService接口中定义了SkyWalking Agent核心服务的行为
  • ServiceManagerBootService实例的管理器,主要负责管理BootService实例的生命周期;ServiceManager是个单例,底层维护了一个bootedServices集合(Map<Class, BootService> 类型),记录了每个BootService实现对应的实例。
  • boot()方法是ServiceManager的核心方法
  1. 首先通过load方法实例化全部BootService接口实现
  2. apm-agent-core模块的resource/META-INF.services/org.apache.skywalking.apm.agent.core.boot.BootService文件中,记录了 ServiceManager要加载的BootService接口实现类
  3. 加载完上述BootService实现类型之后,ServiceManager会针对BootService上的@DefaultImplementor@OverrideImplementor注解进行处理
    • @DefaultImplementor注解用于标识BootService接口的默认实现。
    • @OverrideImplementor注解用于覆盖默认BootService实现,通过value字段指定要覆盖的默认实现。
  4. 确定完要使用的BootService实现之后,ServiceManager将统一初始化bootServices集合中的BootService实现,同样是在ServiceManager.boot()方法中,逐个调用BootService实现的prepare()startup()onComplete()方法
    BootService覆盖逻辑

JVM关闭钩子

Skywalking Agent启动流程的最后,会添加一个JVM退出钩子,并通过ServiceManager.shutdown()方法,关闭前文启动的全部BootService服务。

扫描二维码,分享此文章