skywalking-agent初始化流程(一)-插件加载和插件

2020-08-10  本文已影响0人  李亚林1990

写在开篇前:
github地址:https://github.com/apache/skywalking.git
介绍的skywalking版本:6.5.0

agent配置参数:
java -javaagent://Users/xxx/agent/skywalking-agent.jar -jar helloword.jar

查看skywalking-agent.jar::META-INF/MANIFEST.MF
找到agent入口类:org.apache.skywalking.apm.agent.SkyWalkingAgent


image.png

提取SkyWalkingAgent.premain主要代码

public static void premain(String agentArgs, Instrumentation instrumentation) {
        //解析“/config/agent.config”文件,加载agent配置到Config.Agent.class
        SnifferConfigInitializer.initialize(agentArgs);
        
        //根据配置内容加载agent插件
        final PluginFinder pluginFinder;
        pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
        
        //使用上一步加载到的插件定义transformer,生效每一个插件的匹配规则、拦截逻辑
        agentBuilder
            .type(pluginFinder.buildMatch())
            .transform(new Transformer(pluginFinder))
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .with(new Listener())
            .installOn(instrumentation);
        
        //启动一系列agent本地服务、线程
        ServiceManager.INSTANCE.boot();
        
        //关闭服务
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override public void run() {
                ServiceManager.INSTANCE.shutdown();
            }
        }, "skywalking service shutdown thread"));
}

本小节梳理agent插件加载流程

1.PluginBootstrap.loadPlugins 插件配置文件解析

public List<AbstractClassEnhancePluginDefine> loadPlugins() {
        //查找class路径下所有的skywalking-plugin.def文件
        PluginResourcesResolver resolver = new PluginResourcesResolver();
        List<URL> resources = resolver.getResources();
        
        //解析配置文件内容到List<PluginDefine>
        //PluginDefine.class包含了插件名称name和插件定义类名称defineClass
        for (URL pluginUrl : resources) {
            try {
                PluginCfg.INSTANCE.load(pluginUrl.openStream());
            } catch (Throwable t) {
                logger.error(t, "plugin file [{}] init failure.", pluginUrl);
            }
        }
        List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();

        //根据List<PluginDefine>实例化插件配置类
        List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
        for (PluginDefine pluginDefine : pluginClassList) {
                AbstractClassEnhancePluginDefine plugin =(AbstractClassEnhancePluginDefine)Class.forName(pluginDefine.getDefineClass(),
                        true,
                        AgentClassLoader.getDefault())
                        .newInstance();
                plugins.add(plugin);
        }
        //另一种配置方式加载插件
        plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));

        return plugins;
    }

2.以dubbo插件为例来看一下插件定义类AbstractClassEnhancePluginDefine需要声明哪些内容
插件工程结构:

image.png
skywalking-plugin.def文件内容:
image.png

dubbo插件定义:DubboInstrumentation.class
声明两个要素:待增强类的匹配规则、待增强的方法和拦截处理器

public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
    private static final String ENHANCE_CLASS = "org.apache.dubbo.monitor.support.MonitorFilter";

    private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor";

    @Override
    protected ClassMatch enhanceClass() {
        return NameMatch.byName(ENHANCE_CLASS);
    }

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return null;
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new InstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                    return named("invoke");
                }

                @Override
                public String getMethodsInterceptor() {
                    return INTERCEPT_CLASS;
                }

                @Override
                public boolean isOverrideArgs() {
                    return false;
                }
            }
        };
    }
}

skywalking agent针对静态方法和实例方法增强分别定义了两个不同的抽象模版类,都继承自ClassEnhancePluginDefine.class
ER图如下:


image.png

enhanceClass方法:待增强类的匹配规则
getInstanceMethodsInterceptPoints方法:待增强的实例方法和拦截处理器
getConstructorsInterceptPoints方法:待增强的构造方法和拦截处理器
getStaticMethodsInterceptPoints方法:待增强的静态方法和拦截处理器

同样对于静态方法拦截器和实例方法拦截器也定义了InstanceMethodsAroundInterceptor和StaticMethodsAroundInterceptor两个接口,都定义了beforeMethod、afterMethod、handleMethodException三个待实现方法。
区别在于:
静态拦截器中模版方法的第一个参数为Class clazz,便于我们在拦截逻辑中调用父类的静待方法,相当于super指针;
实例方法拦截器中模版方法的第一个参数为EnhancedInstance objInst,便于我们在拦截逻辑中调用父类的实例想法,相当于this指针。

对于为什么这里提到只能调用父类的静态方法和实例方法,基于我的理解如下:

首先,从字节码文件结构本身最基本的认知来说,肯定是能在待增强方法的字节码序列中插入invokestatic、invokevirtual等指令调用指定的方法描述符(CONSTANT_NameAndType_info)的。
但从skywalking的agent实现上来说,我们在增强逻辑代码中是拿不到待增强类的类信息的,因为类增强的触发点为类信息加载后,而代码逻辑中要拿到一个类的类信息必须在加载、链接(验证、准备、解析)、初始化后。笔者在工程实践中的测试结果为,将上述拦截方法的第一个参数clazz或者objInst强转为对应的类,从而在拦截逻辑中使用类信息的话,都会报如下类重定义异常。

java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name

但在IDEA开发工具中直接运行代码则不会报重定义异常,分析为IDEA中直接运行的程序会在java程序启动命令中自动加上一些-javaagent的热部署插件。

上一篇下一篇

猜你喜欢

热点阅读