美文网首页
skywalking-agent初始化流程(一)-插件加载和插件

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

作者: 李亚林1990 | 来源:发表于2020-08-10 20:27 被阅读0次

    写在开篇前:
    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的热部署插件。

    相关文章

      网友评论

          本文标题:skywalking-agent初始化流程(一)-插件加载和插件

          本文链接:https://www.haomeiwen.com/subject/wqtqdktx.html