微内核架构 和 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
服务。
扫描二维码,分享此文章