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

- 内核系统:负责管理插件的生命周期,保证系统不因功能扩展而不断进行修改。
- 插件模块:独立存在的模块,包括特定的功能,用于扩展核心系统的功能。相互模块之间应保持最小依赖原则,避免过分依赖带来扩展性问题。
SPI机制
- 服务的提供者提供了接口的实现之后,在
Classpath的META-INF/services/目录里创建以服务接口命名的文件,此文件记录了该jar包提供的服务接口的具体实现类。 - 当某个应用引入了该jar包且需要使用该服务时,SPI机制通过查找这个jar包的
META-INF/services/中配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。 -
JDK SPI加载步骤
JDK SPI的入口方法是ServiceLoader.load()方法LazyIterator.hasNextService()方法,主要负责查找META-INF/services目录下的SPI配置文件,并进行遍历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启动流程
- 初始化配置信息: 该步骤中会加载
agent.config配置文件,其中会检测Java Agent参数以及环境变量是否覆盖了相应配置项。 - 查找并解析
skywalking-plugin.def插件文件。 AgentClassLoader加载插件PluginFinder对插件进行分类管理。- 使用
Byte Buddy库创建AgentBuilder。根据已加载的插件动态增强目标类,插入埋点逻辑。 - 使用
JDK SPI加载并启动BootService服务 - 添加一个JVM钩子,在JVM退出时关闭所有
BootService服务。
配置读取及处理
SnifferConfigInitializer.initializeCoreConfig()方法中会将配置信息填充到Config静态字段中
- 按照
${配置项名称:默认值}的格式读取各个配置项。 - 解析系统环境变量值,覆盖
Config中相应的静态字段。 - 解析
Java Agent的参数,覆盖Config中相应的静态字段。 - 将
agent.config文件中全部配置信息填充到Config中相应的静态字段中。 - 检测
SERVICE_NAME和BACKEND_SERVICE两个配置项,若为空则抛异常 - 更新初始化标记
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中的默认值
插件启动流程
- 加载插件时使用到一个自定义的
ClassLoader—AgentClassLoader - 使用自定义类加载器,目的是不在应用的
Classpath中引入SkyWalking的插件jar包,让应用无依赖、无感知插件。
- 在
AgentClassLoader的构造方法中初始化classpath字段,该字段指向AgentClassLoader要扫描的目录(skywalking-agent.jar包同级别plugins目录和activations目录) - 从Classpath下加载类(或资源文件);对应
findClass()方法和findResource()方法。 findClass()方法会扫描所有jar包,查找类文件findResource()方法会遍历allJars集合缓存的全部jar包,从中查找指定的资源文件并返回AgentClassLoader中有一个DEFAULT_LOADER静态字段,记录了默认的AgentClassLoader
插件解析器
- 每个Agent插件中都会定义一个
skywalking-plugin.def文件 PluginResourcesResolver是Agent插件的资源解析器,会通过AgentClassLoader中的findResource()方法读取所有Agent插件中的skywalking-plugin.def文件
Agent增强类
- 拿到全部插件的
skywalking-plugin.def文件之后,PluginCfg会逐行进行解析,转换成PluginDefine对象;PluginCfg是通过枚举实现的、单例的工具类 - 遍历全部
PluginDefine对象,通过反射将其中defineClass字段中记录的插件类实例化;使用类加载器是默认的AgentClassLoader实例
AbstractClassEnhancePluginDefine抽象类是所有Agent插件类的顶级父类,定义了四个核心方法,决定了一个插件类应该增强哪些目标类、应该如何增强、具体插入哪些逻辑。enhanceClass()方法:返回的ClassMatch,用于匹配当前插件要增强的目标类。define()方法:插件类增强逻辑的入口,底层会调用enhance()方法和witnessClass()方法。enhance()方法:真正执行增强逻辑的地方。witnessClass()方法:一个开源组件可能有多个版本,插件会通过该方法识别组件的不同版本,防止对不兼容的版本进行增强。
增强目标匹配类
enhanceClass()方法决定了一个插件类要增强的目标类,返回值为ClassMatch类型对象。ClassMatch类似于一个过滤器,可以通过多种方式匹配到目标类NameMatch:根据其className字段(String 类型)匹配目标类的名称。IndirectMatch:子接口中定义了两个方法。MultiClassNameMatch:其中会指定一个matchClassNames集合,该集合内的类即为目标类。ClassAnnotationMatch:根据标注在类上的注解匹配目标类。MethodAnnotationMatch:根据标注在方法上的注解匹配目标类。HierarchyMatch:根据父类或是接口匹配目标类。
插件查找器
PluginFinder是AbstractClassEnhancePluginDefine查找器,可以根据给定的类查找用于增强的AbstractClassEnhancePluginDefine集合。- 在
PluginFinder的构造函数中会遍历已经实例化的AbstractClassEnhancePluginDefine,并根据enhanceClass()方法返回的ClassMatcher类型进行分类- 如果返回值为
NameMatch类型,则相应AbstractClassEnhancePluginDefine对象会记录到该集合nameMatchDefine - 如果是其他类型返回值,则相应
AbstractClassEnhancePluginDefine对象会记录到该集合signatureMatchDefine
- 如果返回值为
find()方法是PluginFinder对外暴露的查询方法,其中会先后遍历nameMatchDefine集合和signatureMatchDefine集合,通过ClassMatch.isMatch()方法确定所有的匹配插件
Agent构造器
- Byte Buddy如何使用加载到的插件类增强目标方法
- 首先会创建
ByteBuddy对象:Config.Agent.IS_OPEN_DEBUGGING_CLASS如果将其配置为 true,则会将动态生成的类输出到 debugging 目录中。 - 接下来创建
AgentBuilder对象,AgentBuilder是Byte Buddy库专门用来支持 Java Agent 的一个 API,相关方法ignore()方法:忽略指定包中的类,对这些类不会进行拦截增强。type()方法:在类加载时根据传入的ElementMatcher进行拦截,拦截到的目标类将会被transform()方法中指定的Transformer进行增强。transform()方法:这里指定的Transformer会对前面拦截到的类进行增强。with()方法:添加一个Listener用来监听``AgentBuilder`触发的事件。
PluginFInder.buildMatch()方法返回的ElementMatcher对象会将全部插件的匹配规则(即插件的enhanceClass()方法返回的 ClassMatch)用OR的方式连接起来,这样,所有插件能匹配到的所有类都会交给Transformer处理。with()方法中添加的监听器——SkywalkingAgent.Listener,它继承了AgentBuilder.Listener接口,当监听到Transformation事件时,会根据IS_OPEN_DEBUGGING_CLASS配置决定是否将增强之后的类持久化成class文件保存到指定的log目录中; 注意,该操作是需要加锁的,会影响系统的性能,一般只在测试环境中开启,在生产环境中不会开启。Skywalking.Transformer,它实现了AgentBuilder.Transformer接口,其transform()方法是插件增强目标类的入口。Skywalking.Transformer会通过PluginFinder查找目标类匹配的插件(即AbstractClassEnhancePluginDefine对象),然后交由AbstractClassEnhancePluginDefine完成增强注意:如果一个类被多个插件匹配会被增强多次,当你打开
IS_OPEN_DEBUGGING_CLASS配置项时,会看到对应的多个class文件。
服务管理
JDK SPI技术加载BootService接口的所有实现类,BootService接口中定义了SkyWalking Agent核心服务的行为ServiceManager是BootService实例的管理器,主要负责管理BootService实例的生命周期;ServiceManager是个单例,底层维护了一个bootedServices集合(Map<Class, BootService>类型),记录了每个BootService实现对应的实例。boot()方法是ServiceManager的核心方法
- 首先通过load方法实例化全部
BootService接口实现 - 在
apm-agent-core模块的resource/META-INF.services/org.apache.skywalking.apm.agent.core.boot.BootService文件中,记录了ServiceManager要加载的BootService接口实现类 - 加载完上述
BootService实现类型之后,ServiceManager会针对BootService上的@DefaultImplementor和@OverrideImplementor注解进行处理@DefaultImplementor注解用于标识BootService接口的默认实现。@OverrideImplementor注解用于覆盖默认BootService实现,通过value字段指定要覆盖的默认实现。
- 确定完要使用的
BootService实现之后,ServiceManager将统一初始化bootServices集合中的BootService实现,同样是在ServiceManager.boot()方法中,逐个调用BootService实现的prepare()、startup()、onComplete()方法
JVM关闭钩子
在 Skywalking Agent启动流程的最后,会添加一个JVM退出钩子,并通过ServiceManager.shutdown()方法,关闭前文启动的全部BootService服务。
扫描二维码,分享此文章