SkyWalking8源码(二)客户端启动与类加载
前言
本章基于SkyWalking8.6.0分析javaagent客户端:
- 梳理agent启动流程;
- 关注类加载设计;
一、主流程
SkyWalkingAgent#premain:agent启动主流程。
主流程分为五步:
- 加载配置注入Config;
- 加载skywalking-plugin.def对应class注入PluginFinder;
- 使用ByteBuddy定义class增强逻辑,安装到Instrumentation;
- ServiceManager加载并启动所有BootService;
- 注册ShutdownHook;
二、加载配置
SnifferConfigInitializer#initializeCoreConfig:加载配置。
配置取自于三个地方,优先级从低到高依次是:agent.config文件、-D参数、agent参数。
agent.config配置,如agent.service_name。
- 支持占位符替换,如SW_AGENT_NAME,取值来源:-D参数>环境变量>agent.config;
- 支持默认值;
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
-D参数配置,需要加上skywalking前缀,如:
java -Dskywalking.agent.service_name=servicexxx -javaagent:agent.jar -jar app.jar
agent参数配置,逗号分割配置项,等号分割kv对:
java -javaagent:agent.jar=agent.service_name=a -jar app.jar
所有的配置项都会注入全局配置类Config。
如agent.service_name注入Config$Agent.SERVICE_NAME。
三、加载skywalking-plugin.def
1、什么是skywalking-plugin.def
对于每个skywalking插件,都会在skywalking-plugin.def资源文件中配置插件定义。
所有插件都继承自AbstractClassEnhancePluginDefine类。
AbstractClassEnhancePluginDefine是个抽象类。
AbstractClassEnhancePluginDefine#define方法是个模板方法,是class增强定义的主流程。
对于插件的AbstractClassEnhancePluginDefine的实现类,只需要声明:增强哪里、如何增强。
比如RocketMQ4.x并发消费者增强ConsumeMessageConcurrentlyInstrumentation。
- 增强class:org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently接口的实现类;
- 增强method:consumeMessage;
- 增强实现:org.apache.skywalking.apm.plugin.rocketMQ.v4.MessageConcurrentlyConsumeInterceptor;
public class ConsumeMessageConcurrentlyInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
private static final String ENHANCE_CLASS = "org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently";
private static final String CONSUMER_MESSAGE_METHOD = "consumeMessage";
private static final String INTERCEPTOR_CLASS = "org.apache.skywalking.apm.plugin.rocketMQ.v4.MessageConcurrentlyConsumeInterceptor";
@Override
public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
return new InstanceMethodsInterceptPoint[] {
new InstanceMethodsInterceptPoint() {
@Override
public ElementMatcher<MethodDescription> getMethodsMatcher() {
return named(CONSUMER_MESSAGE_METHOD);
}
@Override
public String getMethodsInterceptor() {
return INTERCEPTOR_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
@Override
protected ClassMatch enhanceClass() {
return HierarchyMatch.byHierarchyMatch(new String[] {ENHANCE_CLASS});
}
}
2、AbstractClassEnhancePluginDefine
AbstractClassEnhancePluginDefine的子类可以通过各种方式描述增强哪些类。
增强哪个class
实现AbstractClassEnhancePluginDefine#enhanceClass:返回一个ClassMatch。
ClassMatch是一个标记接口没有任何方法,有两大类实现:
- NameMatch:根据class完全限定类名匹配;
- IndirectMatch:非直接匹配;
除了NameMatch都是非直接匹配,比如ClassAnnotationMatch根据class上的annotation匹配。
根据版本增强
部分场景下,对于不同版本的同一个class,需要提供不同的增强实现。
AbstractClassEnhancePluginDefine#witnessClasses/witnessMethods:
通过定义witnessClass和Method,根据class或method是否存在,区分版本是否增强当前class。
比如SpringMVC4增强定义:
SpringMVC5增强定义:
是否增强BootstrapClassLoader类
AbstractClassEnhancePluginDefine#isBootstrapInstrumentation:
一般情况下,不需要增强BootstrapClassLoader下的类。
RunnableInstrumentation#isBootstrapInstrumentation:
RunnableInstrumentation需要增强java.util.Runnable。
定义增强点
增强点支持三种:
- ConstructorInterceptPoint:增强构造方法;
getConstructorMatcher:拦截哪些构造方法;
getConstructorInterceptor:增强拦截器完全限定类名;
- InstanceMethodsInterceptPoint/InstanceMethodsInterceptV2Point:增强实例方法;
getMethodsMatcher:拦截那些实例方法;
getMethodsInterceptor:V1增强拦截器完全限定类名,InstanceMethodsAroundInterceptor实现类;
isOverrideArgs:是否重写入参;
getMethodsInterceptorV2:V2增强拦截器完全限定类名,InstanceMethodsAroundInterceptorV2实现类;
- StaticMethodsInterceptPoint/StaticMethodsInterceptV2Point:增强静态方法,和实例方法类似,不再赘述;
V1V2拦截器
在V1拦截器中,handleMethodException方法异常,afterMethod方法执行后,无法拿到beforeMethod中的临时变量。(V1可以通过RuntimeContext这样的ThreadLocal处理)
这类数据其实就是上下文。在V2拦截器中,可以通过MethodInvocationContext在当前拦截器中传递。
3、加载AbstractClassEnhancePluginDefine
PluginBootstrap#loadPlugins:
将所有skywalking-plugin.def加载为AbstractClassEnhancePluginDefine。
创建默认ClassLoader
AgentClassLoader#initDefaultLoader:
skywalking中大部分类都是由一个全局AgentClassLoader加载的,后文称这个ClassLoader为默认ClassLoader。
默认ClassLoader的父类加载器就是agent的类加载器,即AppClassLoader。
AgentClassLoader可以加载plugins和activations目录下的插件。
PluginDefine
使用默认ClassLoader加载classpath下所有skywalking-plugin.def。
每行skywalking-plugin.def都会成为一个PluginDefine对象。
如tomcat要加载TomcatInstrumentation和ApplicationDispatcherInstrumentation。
tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.TomcatInstrumentation
tomcat-7.x/8.x=org.apache.skywalking.apm.plugin.tomcat78x.define.ApplicationDispatcherInstrumentation
加载AbstractClassEnhancePluginDefine
最终使用默认ClassLoader加载AbstractClassEnhancePluginDefine。
AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine)
Class.forName(pluginDefine.getDefineClass(), true,
AgentClassLoader.getDefault()).newInstance();
所以AbstractClassEnhancePluginDefine实现类只能加载这些插件:
- AppClassLoader:skywalking-agent.jar里的class,classpath里的class;
- 默认ClassLoader:plugins和activations目录下的class;
注入PluginFinder
PluginFinder构造,将AbstractClassEnhancePluginDefine划分为三个集合:
- nameMatchDefine:通过className精确匹配的插件定义,即enhanceClass返回的ClassMatch类型为NameMatch;
- signatureMatchDefine:enhanceClass返回的ClassMatch类型为IndirectMatch的插件;
- bootstrapClassMatchDefine:需要在BootStrapClassLoader中增强的插件;
四、安装增强
注意这里只是对class增强的定义,实际对class增强是在类加载阶段。
1、主流程
SkyWalkingAgent#premain:
先处理PluginFinder中bootstrapClassMatchDefine集合中需要BootstrapClassLoader增强的插件。
这部分插件在bootstrap-plugins目录下,默认不会启用,这段逻辑忽略。
注:如上面创建默认ClassLoader所述,默认ClassLoader只能加载plugins和activations目录下的class。
SkyWalkingAgent#premain:接下来操作ByteBuddy的api安装增强
- type:定义增强哪些class;
- Transformer:定义如何增强;
- Listener:debug相关;
2、buildMatch定义增强哪些类
PluginFinder#buildMatch:匹配条件通过or关联,组合所有插件需要增强的class
- nameMatchDefine,NameMatch,插件通过className精确匹配;
- signatureMatchDefine,IndirectMatch,插件自己构造匹配条件Junction;
ProtectiveShieldMatcher代理匹配条件Junction,发生任何插件的匹配逻辑异常,都会降级返回false不匹配。
3、Transformer定义如何增强(重要)
主流程
Transformer实现AgentBuilder.Transformer(bytebuddy)的transform方法定义如何增强一个类。
入参:
- builder:增强构造builder;
- typeDescription:被增强的类描述;
- classLoader:被增强的类的类加载器,比如springboot应用中的LaunchedURLClassLoader;
SkyWalkingAgent.Transformer#transform:
- 根据类描述TypeDescription找到类增强定义AbstractClassEnhancePluginDefine;
- 执行AbstractClassEnhancePluginDefine#define方法构建增强;
AbstractClassEnhancePluginDefine#define:
如果witnessClasses/witnessMethods返回class或method在增强类的ClassLoader下不存在,则不增强;
AbstractClassEnhancePluginDefine#enhance:分别增强静态方法和实例方法。
以实例方法增强为例,这里V1和V2的区别仅仅在于Interceptor的实现不同,看V1。
实现EnhancedInstance
ClassEnhancePluginDefine#enhanceInstance:
对于实例方法增强,所有class都会实现EnhancedInstance接口。
EnhancedInstance
对于这些类新增一个Object类型_$EnhancedClassField_ws
属性,实现EnhancedInstance。
构造增强
构造增强的拦截逻辑都委派到ConstructorInter。
ConstructorInter构造,传入拦截器完全限定类名和增强类的ClassLoader。
通过InterceptorInstanceLoader加载插件拦截器。
InterceptorInstanceLoader非常重要,是agent自己ClassLoader到应用ClassLoader的桥梁。
如果拦截器也用默认ClassLoader加载(见上面默认AgentClassLoader),那么拦截器里就无法加载应用的class,比如SpringBoot的LaunchedURLClassLoader加载的class。
所以拦截器的类加载器需要特殊定制。
- INSTANCE_CACHE:插件拦截器instance缓存;
- EXTEND_PLUGIN_CLASSLOADERS:被增强的ClassLoader到Agent的ClassLoader的映射;
InterceptorInstanceLoaderload:假设当前是个springboot应用,传入targetClassLoader就是LaunchedURLClassLoader。
public static <T> T load(String className,
ClassLoader targetClassLoader) throws IllegalAccessException, InstantiationException, ClassNotFoundException, AgentPackageNotFoundException {
if (targetClassLoader == null) {
targetClassLoader = InterceptorInstanceLoader.class.getClassLoader();
}
String instanceKey = className + "_OF_" + targetClassLoader.getClass()
.getName() + "@" + Integer.toHexString(targetClassLoader
.hashCode());
Object inst = INSTANCE_CACHE.get(instanceKey);
if (inst == null) {
INSTANCE_LOAD_LOCK.lock();
ClassLoader pluginLoader;
try {
pluginLoader = EXTEND_PLUGIN_CLASSLOADERS.get(targetClassLoader);
if (pluginLoader == null) {
pluginLoader = new AgentClassLoader(targetClassLoader);
EXTEND_PLUGIN_CLASSLOADERS.put(targetClassLoader, pluginLoader);
}
} finally {
INSTANCE_LOAD_LOCK.unlock();
}
inst = Class.forName(className, true, pluginLoader).newInstance();
if (inst != null) {
INSTANCE_CACHE.put(instanceKey, inst);
}
}
return (T) inst;
}
对于每个目标ClassLoader会构造一个新的AgentClassLoader并缓存,这个新的AgentClassLoader的parent是目标ClassLoader。
通过这种方式,插件拦截器即可以加载agent中的class,也可以加载应用的class。
ConstructorInter#intercept:运行阶段,构造拦截可以拿到被增强的对象obj和构造参数allArguments。
注:对于增强异常会try-catch。
实例方法增强
ClassEnhancePluginDefine#enhanceInstance:实例方法增强分为两种:
- 重写入参,使用InstMethodsInterWithOverrideArgs;
- 不重写入参,使用InstMethodsInter;
以InstMethodsInter为例。
InstMethodsInter构造还是会用插件类加载器(见上面构造拦截)加载拦截器。
InstMethodsInter#intercept:运行阶段
- 可以拿到目标对象、方法入参、原始方法调用函数(zsuper)、目标方法;
- 所有拦截器发生的异常都会try-catch;
- 通过MethodInterceptResult可以在before拦截返回值;
五、BootService
1、什么是BootService
BootService是apm-agent-core模块(skywalking-agent.jar)下的一个接口。
- prepare、boot、onComplete:启动阶段调用;
- shutdown:进程关闭阶段调用;
- priority:优先级;
public interface BootService {
void prepare() throws Throwable;
void boot() throws Throwable;
void onComplete() throws Throwable;
void shutdown() throws Throwable;
default int priority() {
return 0;
}
}
SkyWalkingAgent#premain:ServiceManager管理所有BootService实现。
ServiceManager#boot:ServiceManager在agent启动最后阶段,加载所有BootService并依次调用BootService的prepare、boot、onComplete方法。
ServiceManager#findService:BootService之间可以通过ServiceManager做依赖发现。
2、加载BootService
ServiceManager#loadAllServices:
- 加载BootService实现类;
- 处理DefaultImplementor和OverrideImplementor逻辑,注入Map,key是BootService的实现类,value是对应实例;
ServiceManager#load:BootService用JDK的SPI加载,采用默认ClassLoader,不能加载应用的class。
注:并不是BootService都是由默认AgentClassLoader加载的,大部分BootService实现就在核心skywalking-agent.jar中,默认AgentClassLoader会委派到AppClassLoader加载。
DefaultImplementor注解用于修饰一个默认BootService实现。
OverrideImplementor注解用于修饰重写一个默认BootService。
比如上面这两个例子,agent有两种方式发送segment到OAP:
- TraceSegmentServiceClient:agent直连OAP,走gRPC发送,这个实现在skywalking-agent.jar中,所以是AgentClassLoader委派给AppClassLoader加载;
- KafkaTraceSegmentServiceClient:agent发送到Kafka,OAP从Kafka消费,这个实现在optional-reporter-plugins/kafka-reporter-plugin.jar中,不会被默认AgentClassLoader加载到,默认不生效,如果将kafka-reporter-plugin.jar放入plugins目录下,则可以由默认AgentClassLoader加载;
3、启动BootService
ServiceManager#prepare/startup/onComplete:
ServiceManager会依次调用BootService的prepare、boot、onComplete方法。
- priority越小越优先调用;
- onComplete方法不受优先级影响;
- 异常被try-catch;
ServiceManager#shutdown:进程停止反向调用所有BootService的shutdown方法。
六、其他
1、插件自定义配置
agent可以配置众多plugin配置。
在agent核心配置类Config中,Plugin只关联少部分配置。
其余plugin配置都在各自插件中维护,通过PluginConfig注解注入。
AgentClassLoader#findClass:当class被AgentClassLoader加载,都会触发AgentClassLoader#processLoadedClass
AgentClassLoader#processLoadedClass:
这里会利用启动阶段拿到的所有kv配置,反射注入PluginConfig注解的类。
2、SkyWalkingAgent.Listener
安装增强阶段加入了SkyWalkingAgent.Listener。
SkyWalkingAgent.Listener#onTransformation:
InstrumentDebuggingClass#log:通过开启agent.is_open_debugging_class,可以将增强的class保存到skywalking-agent.jar的debugging目录下
反编译可以追溯增强情况。
总结
启动流程
- 加载配置到全局Config类,优先级agent.config<-D参数<agent参数
-
- agent.config,支持占位符,占位符取值来源:-D参数>环境变量>agent.config;
- -D参数,在原始配置前要加skywalking前缀,如-Dskywalking.agent.service_name;
- agent参数,逗号分割配置项,等号分割kv,如agent.jar=k1=v1,k2=v2;
- 加载skywalking-plugin.def
-
- 全局默认AgentClassLoader加载插件,能加载plugins和activations下的jar里的资源和class,其parent为AppClassLoader;
- skywalking-plugin.def是插件定义文件,文件每行都会成为一个PluginDefine;
- 根据PluginDefine中的className,全局默认AgentClassLoader加载插件AbstractClassEnhancePluginDefine定义;
- 定义class增强
-
- 操作ByteBuddy api定义Instrument增强逻辑;
- 增强哪些类,组合插件定义的ClassMatch构建bytebuddy的ElementMatcher;
- 如何增强,SkyWalkingAgent.Transformer实现bytebuddy的AgentBuilder.Transformer,在class加载时执行增强逻辑;
- 启动BootService:
-
- 通过JDK SPI加载;
- 全局默认AgentClassLoader加载BootService;
- 依次调用BootService的prepare、boot、onComplete方法;
- prepare、boot可以通过优先级控制;
- 注册ShutdownHook,执行BootService#shutdown;
增强类加载
SkyWalkingAgent.Transformer在运行时类加载阶段增强class。
循环class的每个增强插件定义AbstractClassEnhancePluginDefine。
- 如果witnessClasses或witnessMethods返回的class或method无法找到,跳过该插件,解决组件多版本问题;
- 依次增强静态方法和实例方法;
- 对于实例方法增强的class,都会实现EnhancedInstance接口,新增一个成员变量
_$EnhancedClassField_ws
;
-
构造拦截器;
- 插件定义会声明增强拦截器实现,比如InstanceMethodsAroundInterceptor拦截实例方法;
- 插件拦截器会被包装一层,比如InstMethodsInter包装InstanceMethodsAroundInterceptor,控制实例方法执行的流程;
- InstMethodsInter构造会用InterceptorInstanceLoader#load加载插件拦截器,这是agent类加载器到用户classLoader的桥梁;
- InstMethodsInter在实例方法执行阶段,会try-catch插件拦截器的所有执行逻辑;
类加载器
以最常见的springboot应用来说,skywalking-agent的类加载情况如下。
- skywalking-agent.jar中的class都由AppClassLoader加载;
- 插件定义和BootService都由全局默认AgentClassLoader加载;
- 插件拦截器由独立AgentClassLoader加载,这个AgentClassLoader的parent是用户增强的类的原始加载器,如springboot的LaunchedURLClassLoader;
- AgentClassLoader自己是满足双亲委派的,即全局默认AgentClassLoader会先委派AppClassLoader,插件拦截器的独立AgentClassLoader会先委派LaunchedURLClassLoader;
欢迎大家评论或私信讨论问题。
本文原创,未经许可不得转载。
欢迎关注公众号【程序猿阿越】。
转载自:https://juejin.cn/post/7382891974955417637