关于java:Skywalking光会用可不行必须的源码分析分析-Skywalking-Agent-插件解析

2次阅读

共计 21893 个字符,预计需要花费 55 分钟才能阅读完成。

3 Skywalking 源码导入

接上文,曾经学习了 Skywalking 的利用,接下来咱们将分析 Skywalking 源码,深度学习 Skywalking Agent。

3.1 源码环境搭建

以后最新版本是 8.3.0, 咱们首先找到 8.3.0 的版本,而后下载并导入到 IDEA,下载地址

https://github.com/apache/sky…,咱们间接用 git 克隆到本地。

1、举荐大家将 github 仓库拷贝到码云上,以晋升下载速度

2、为了防止 clone 过程出错,能够设置 git 的全局参数:git config --global core.longpaths true避免出现Filename too long 的报错信息

1)下载工程

这个过程比拟耗时间,须要大家急躁期待!

2)切换版本

将 Skywalking 工程退出到 Maven 工程中,咱们用的是以后最新版本 8.3.0, 因而须要切换版本:

我的项目导入 IDEA 后,会从指定门路加载我的项目,咱们须要在 skywalking 的 pom.xml 中配置我的项目的工门路,增加如下 properties 配置即可:

<maven.multiModuleProjectDirectory>C:\developer\WorkSpace\skywalking</maven.multiModuleProjectDirectory>

pom 中有一个插件 maven-enforcer-plugin 要求的 maven 的版本是 3.6 以上,须要留神!!!

咱们接下来获取 skywalking 子模块的源码,须要在工程中执行如下命令:

git submodule init
git submodule update

该步骤十分重要,不残缺执行胜利,后续的编译会失败。git submodule update 执行很慢,还可能中途中断

编译我的项目,此时会生成一些类 skywalking\apm-protocol\apm-network\target\generated-sources\protobuf\java\org\apache\skywalking\apm\network\common\v3 目录下的类如下图:

接下来把生成的文件增加到类门路下,如下图:

除了下面这里,还有很多个中央都须要这么操作,咱们执行 OAPServerStartUp 的 main 办法启动 Skywalking,只有执行找不到类,就找下有没有任何编译后生成的类没在类门路下,都把他们设置为类门路即可。

装置我的项目,Skywalking 依赖的插件特地多,因而依赖的包也特地多,咱们把 Skywalking 装置到本地,会消耗很长时间,但不要放心,因为迟早会装置实现,如下图:

如果通过以上形式切实构建不了源码,也可尝试通过如下形式来,

官网提供了对于如何构建的步骤能够参阅:

https://github.com/apache/sky…

社区中文版:https://skyapm.github.io/docu…

1、通过命令拉取源码

git clone -b v8.3.0 --recurse-submodules https://gitee.com/giteets/skywalking.git

构建过程中遇到的最大问题是:git submodule 子模块的源码构建不进去,整体我的项目拉取下来后也可通过如下命令再次拉取子模块源码

git submodule init
git submodule update

如果切实不行:在我的项目下有个 .gitmodules 文件,定义了子模块的仓库地址和应该装置到什么目录下

[submodule "apm-protocol/apm-network/src/main/proto"]
   path = apm-protocol/apm-network/src/main/proto
   url = https://github.com/apache/skywalking-data-collect-protocol.git
[submodule "oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol"]
   path = oap-server/server-query-plugin/query-graphql-plugin/src/main/resources/query-protocol
   url = https://github.com/apache/skywalking-query-protocol.git
[submodule "skywalking-ui"]
   path = skywalking-ui
   url = https://github.com/apache/skywalking-rocketbot-ui.git
[submodule "test/e2e/e2e-protocol/src/main/proto"]
   path = test/e2e/e2e-protocol/src/main/proto
   url = https://github.com/apache/skywalking-data-collect-protocol.git

切实不行,就手动将这四个子模块别离手动下载到指定的 path 目录下,留神版本

2、将我的项目导入到 idea,要求 jkd8,maven3.6

3、在我的项目的 pom.xml 中增加properties

<maven.multiModuleProjectDirectory>C:\developer\WorkSpace\skywalking</maven.multiModuleProjectDirectory>

4、cleanpackageinstall,留神跳过测试

5、参考社区文档,设置 idea,将生成的源代标记成Sources Root

设置 生成的源代码 (Generated Source Code) 目录.

  • apm-protocol/apm-network/target/generated-sources/protobuf 目录下的 grpc-javajava 目录
  • oap-server/server-core/target/generated-sources/protobuf 目录下的 grpc-javajava 目录
  • oap-server/server-receiver-plugin/receiver-proto/target/generated-sources/protobuf 目录下的 grpc-javajava
  • oap-server/exporter/target/generated-sources/protobuf 目录下的 grpc-javajava
  • oap-server/server-configuration/grpc-configuration-sync/target/generated-sources/protobuf 目录下的 grpc-javajava

3.2 模块剖析

apm-application-toolkit: 罕用的工具工程,例如:log4j、log4j2、logback 等常见日志框架的接入接口,Kafka 轮询调用注解,apm-application-toolkit 模块相似于裸露 API 定义,对应的解决逻辑在 apm-sniffer/apm-toolkit-activation 模块中实现, 如下图:

apm-commons:SkyWalking 的公共组件和工具类。如下图所示,其中蕴含两个子模块,apm-datacarrier 模块提供了一个生产者 - 消费者模式的缓存组件(DataCarrier),无论是在 Agent 端还是 OAP 端都依赖该组件。apm-util 模块则提供了一些罕用的工具类,例如,字符串解决工具类(StringUtil)、占位符解决的工具类(PropertyPlaceholderHelper、PlaceholderConfigurerSupport)等等。

apache-skywalking-apm:SkyWalking 打包后应用的命令文件都在此目录中,例如,前文启动 OAP 和 SkyWalking Rocketbot 应用的 startup.sh 文件。

apm-protocol:该模块中只有一个 apm-network 模块,咱们须要关注的是其中定义的 .proto 文件,定义 Agent 与后端 OAP 应用 gRPC 交互时的协定。

apm-sniffer:agent 外围性能以及 agent 依赖插件,模块比拟多:

apm-agent: 只有一个类 SkyWalkingAgent,是 Skywalking 的 agent 入口。apm-agent-core: 看名字咱们就晓得它是 Skywalking agent 外围模块。apm-sdk-plugin: 该模块下蕴含了 SkyWalking Agent 的全副插件。apm-toolkit-activation:apm-application-toolkit 模块的具体实现。apm-test-tools:Skywalking 的测试性能。bootstrap-plugins: 该插件次要提供了 Http 和多线程相干的性能反对,它外面有 2 个子工程。optional-plugins: 可选插件,例如对 spring 反对、对 kotlin 反对等,它上面有多个插件工程实现。optional-reporter-plugins: 该工程插件次要提供一些数据报告,集成了 Kafka 性能。

apm-webapp:SkyWalking Rocketbot 对应的后端。

oap-server:oap 主程序,该工程中有多个模块,咱们对外围模块进行阐明:

analyzer: 数据分析工程,例如对内存剖析、仪表盘剖析报告等,它上面有 2 个子工程。exporter: 导出数据性能。oal-grammar: 操作适配语法,例如 SQL 语法。oal-rt: 操作解析器,下面提供了语法,该工程提供对操作解析性能。server-alarm-plugin: 负责实现 SkyWalking 的告警性能。server-cluster-plugin:OAP 集群治理性能,提供了很多第三方染指的组件。server-configuration: 负责管理 OAP 的配置信息,也提供了接入多种配置管理组件的相干插件。server-core:SkyWalking OAP 的外围实现都在该模块中。server-library:OAP 以及 OAP 各个插件依赖的公共模块,其中提供了双队列 Buffer、申请远端的 Client 等工具类,这些模块都是对抗于 SkyWalking OAP 体系之外的类库,咱们能够间接拿着应用。server-query-plugin:SkyWalking Rocketbot 发送的申请首先由该模块接管解决,目前该模块只反对 GraphQL 查问。server-receiver-plugin:SkyWalking Agent 发送来的 Metrics、Trace 以及 Register 等写入申请都是首先由该模块接管解决的,不仅如此,该模块还提供了多种接管其余格局写入申请的插件。server-starter:OAP 服务启动的入口。server-storage-plugin:OAP 服务底层能够应用多种存储来保留 Metrics 数据以及 Trace 数据,该模块中蕴含了接入相干存储的插件。skywalking-agent:SkyWalking Agent 编译后生成的 jar 包都会放到该目录中。skywalking-ui:SkyWalking Rocketbot 的前端。

4 Skywalking Agent 启动流程分析

咱们曾经学习了 Skywalking 罕用操作,并且解说了 Java Agent,而且 Skywalking Agent 就是基于 Java Agent 研发而来,咱们接下来深刻学习 Skywalking Agent 架构、原理、罕用组件。

4.1 Skywalking Agent 架构

咱们在学习 Skywalking 之前,先理解一下微内核架构,如下图:

微内核架构(Microkernel Architecture),也被成为插件化架构(Plug-in Architecture), 是一种面向性能进行拆分的可扩展性架构,通常用于实现基于产品(原文为 product-based,指存在多个版本,须要下载安装能力应用,与 web-based 想对应)的利用。

微内核架构的益处:

1: 测试老本降落。从软件工程的角度看,微内核架构将变动的局部和不变的局部拆分,升高了测试的老本,合乎设计模式中的凋谢关闭准则。2: 稳定性。因为每个插件模块绝对独立,即便其中一个插件有问题,也能够保障内核零碎以及其余插件的稳定性。3: 可扩展性。在减少新性能或接入新业务的时候,只须要新增相应插件模块即可;在进行历史性能下线时,也只需删除相应插件模块即可。

微内核的外围零碎设计的关键技术有:插件治理,插件连贯 和 插件通信。

SkyWalking Agent 采纳了微内核架构(Microkernel Architecture),是一种面向性能进行拆分的可扩展性架构。

apm-agent-core: 是 Skywalking Agent 的外围模块
apm-sdk-plugin: 是 Skywalking 须要的各个插件模块

4.2 Skywalking Agent 启动流程

1)启动 OAP

咱们接下来启动 Skywalking oap,咱们在 oap-server\server-starter 或者 oap-server\server-starter-es7 中找到 OAPServerStartUp 类,执行该类的 main 办法即可启动,但默认用的是 H2 存储,如果心愿用 elasticsearch 存储,须要批改被调用的服务 server-bootstrap 的配置文件 application.yml 配置 elasticsearch 地位:

storage:
  #selector: ${SW_STORAGE:h2}
  selector: ${SW_STORAGE:elasticsearch7}
  elasticsearch7:
    nameSpace: ${SW_NAMESPACE:""}
    #clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:localhost:9200}
    clusterNodes: ${SW_STORAGE_ES_CLUSTER_NODES:192.168.211.145:9200}
    ........ 略

存储间接应用上一次课筹备好的 es7 的存储即可。

执行 OAPServerStartUp 的 main 办法不报错就没问题。

2)启动 SkyWalking Rocketbot

apm-webapp 是 Spring Boot 的 Web 我的项目,执行 ApplicationStartUp 中的 main() 办法。失常启动之后,

拜访 http://localhost:8080,看到 SkyWalking Rocketbot 的 UI 界面即为启动胜利。

如果批改启动端口,能够间接批改 application.yml 即可。

3)间接应用源码中的 Agent

我的项目打包会生成 skywalking-agent 目录,外面有skywalking-agent.jar,如下图:

咱们来应用一下后面源码工程中打包生成的skywalking-agent.jar,复制该 jar 包的门路

找到 hailtaxi-parent 我的项目,批改 -javaagent 参数如下

hailtaxi-gateway

-javaagent:C:\developer\WorkSpace\sources\skywalking\skywalking-agent\skywalking-agent.jar
-Dskywalking_config=C:\developer\WorkSpace\sources\skywalking\skywalking-agent\config\agent.config
-Dskywalking.agent.service_name=hailtaxi-gateway

hailtaxi-driverhailtaxi-order 进行雷同配置即可!

全都启动后,查看Skywalking Rocketbot:本地启动,须要期待肯定的工夫

5 Skywalking Agent 源码分析

1、创立 sw-agent-debugger 我的项目:一个一般的 springboot 我的项目即可

2、增加启动 -javaagent 参数

启动的整个办法执行流程如下:

public static void premain(String agentArgs, Instrumentation instrumentation) throws PluginException {
    final PluginFinder pluginFinder;
    try {
        // 初始化加载 agent.config 配置文件,其中会检测 Java Agent 参数以及环境变量是否笼罩了相应配置项
        SnifferConfigInitializer.initializeCoreConfig(agentArgs);
    } catch (Exception e) {
        // try to resolve a new logger, and use the new logger to write the error log here
        LogManager.getLogger(SkyWalkingAgent.class)
                .error(e, "SkyWalking agent initialized failure. Shutting down.");
        return;
    } finally {
        // refresh logger again after initialization finishes
        LOGGER = LogManager.getLogger(SkyWalkingAgent.class);
    }

    try {
        // 治理插件
        pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
    } catch (AgentPackageNotFoundException ape) {LOGGER.error(ape, "Locate agent.jar failure. Shutting down.");
        return;
    } catch (Exception e) {LOGGER.error(e, "SkyWalking agent initialized failure. Shutting down.");
        return;
    }

    // 应用 ByteBuddy 创立 AgentBuilder
    final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));

    // 疏忽拦挡配置
    AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(nameStartsWith("net.bytebuddy.")
                    .or(nameStartsWith("org.slf4j."))
                    .or(nameStartsWith("org.groovy."))
                    .or(nameContains("javassist"))
                    .or(nameContains(".asm."))
                    .or(nameContains(".reflectasm."))
                    .or(nameStartsWith("sun.reflect"))
                    .or(allSkyWalkingAgentExcludeToolkit())
                    .or(ElementMatchers.isSynthetic()));

    JDK9ModuleExporter.EdgeClasses edgeClasses = new JDK9ModuleExporter.EdgeClasses();
    try {agentBuilder = BootstrapInstrumentBoost.inject(pluginFinder, instrumentation, agentBuilder, edgeClasses);
    } catch (Exception e) {LOGGER.error(e, "SkyWalking agent inject bootstrap instrumentation failure. Shutting down.");
        return;
    }

    try {agentBuilder = JDK9ModuleExporter.openReadEdge(instrumentation, agentBuilder, edgeClasses);
    } catch (Exception e) {LOGGER.error(e, "SkyWalking agent open read edge in JDK 9+ failure. Shutting down.");
        return;
    }

    if (Config.Agent.IS_CACHE_ENHANCED_CLASS) {
        try {agentBuilder = agentBuilder.with(new CacheableTransformerDecorator(Config.Agent.CLASS_CACHE_MODE));
            LOGGER.info("SkyWalking agent class cache [{}] activated.", Config.Agent.CLASS_CACHE_MODE);
        } catch (Exception e) {LOGGER.error(e, "SkyWalking agent can't active class cache.");
        }
    }

    //Java Agent 创立代理流程
    agentBuilder.type(pluginFinder.buildMatch())
                .transform(new Transformer(pluginFinder))
                .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
                .with(new Listener())
                .installOn(instrumentation);

    try {
        // 应用 JDK SPI 加载的形式并启动 BootService 服务。ServiceManager.INSTANCE.boot();} catch (Exception e) {LOGGER.error(e, "Skywalking agent boot failure.");
    }
    // 增加一个 JVM 钩子
    Runtime.getRuntime()
            .addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
}

咱们总结一下 Skywalking Agent 启动流程:

1: 初始化配置信息。该步骤中会加载 agent.config 配置文件,其中会检测 Java Agent 参数以及环境变量是否笼罩了相应配置项。2: 查找并解析 skywalking-plugin.def 插件文件。3:AgentClassLoader 加载插件。4:PluginFinder 对插件进行分类管理。5: 应用 Byte Buddy 库创立 AgentBuilder。这里会依据已加载的插件动静加强指标类,插入埋点逻辑。6: 应用 JDK SPI 加载并启动 BootService 服务。7: 增加一个 JVM 钩子,在 JVM 退出时敞开所有 BootService 服务。

这是 org.apache.skywalking.apm.agent.SkyWalkingAgent#premain 的主体工作流程

5.1 配置初始化

-javaagent:D:/project/skywalking/skywalking/apm-sniffer/apm-agent/target/skywalking-agent.jar
-Dskywalking_config=D:/project/skywalking/hailtaxi-parent/hailtaxi-driver/src/main/resources/agent.config
-Dskywalking.collector.backend_service=127.0.0.1:11800

启动 driver 服务的时候,会指定 skywalking-agent.jar 门路,同时会指定 agent.config 配置文件门路,如上配置,此时须要初始化加载该文件,加载流程能够从启动类 SkyWalkingAgent.premain() 办法找答案。

加载解析文件的时候,permain()办法会调用 initializeCoreConfig(String agentOptions)办法,并解析 agent.config 文件,并将文件内容存入到 Properties 中,此时加载是依照 ${配置项名称: 默认值}的格局解析各个配置,如下图:

loadConfig() 办法会优先依据环境变量(skywalking_config)指定的 agent.config 文件门路加载。若环境变量未指定 skywalking_ config 配置,则到 skywalking-agent.jar 同级的 config 目录下查找 agent.confg 配置文件。

解析前后的数据也是不统一的,如下图:

overrideConfigBySystemProp() 办法中会遍历环境变量(即 System.getProperties() 汇合),如果环境变 是以 “skywalking.” 结尾的,则认为是 SkyWalking 的配置,同样会填充到 Config 类中,以笼罩 agent.config 中的默认值。如下图:

ConfigInitializer 工具类,将配置信息填充到 Config 中的动态字段中,SkyWalking Agent 启动所需的全副配置都曾经填充到 Config 中,后续应用配置信息时间接拜访 Config 中的相应动态字段即可。

Config 构造:

Config 中 Agent 类的 SERVICE_NAME 对应 agent.config 中的 agent.service_name=${xxx}

Config 中 Collector 类的 BACKEND_SERVICE 对应 agent.config 中的 agent.backend_service=${xxx}

5.2 插件加载

加载插件执行流程:

1:new PluginBootstrap()
2:PluginBootstrap().loadPlugins()
3:AgentClassLoader.initDefaultLoader();    没有指定类加载器的时候应用 PluginBootstrap.ClassLoader
4: 创立 PluginResourcesResolver 插件加载解析器
5: 将解析的插件存到 List<PluginDefine> pluginClassList,此时只存储了插件的名字和类门路
6: 创立插件实例
7: 将所有插件增加到 Skywalking 内核中

插件加载流程如下:

SkyWalkingAgent.premain() 办法中会执行插件加载,如下代码:

pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());

加载插件的全副具体代码如下:

public class PluginBootstrap {private static final ILog LOGGER = LogManager.getLogger(PluginBootstrap.class);

    /**
     * 加载所有插件
     * load all plugins.
     * @return plugin definition list.
     */
    public List<AbstractClassEnhancePluginDefine> loadPlugins() throws AgentPackageNotFoundException {
        // 初始化 AgentClassLoader
        AgentClassLoader.initDefaultLoader();

        // 创立 PluginResourcesResolver 插件加载解析器
        PluginResourcesResolver resolver = new PluginResourcesResolver();
        // 获取插件门路
        List<URL> resources = resolver.getResources();

        if (resources == null || resources.size() == 0) {LOGGER.info("no plugin files (skywalking-plugin.def) found, continue to start application.");
            return new ArrayList<AbstractClassEnhancePluginDefine>();}

        // 循环加载插件门路
        for (URL pluginUrl : resources) {
            try {
                // 插件会存到 List<PluginDefine> pluginClassList,PluginDefine 中只有插件名字和插件类门路
                PluginCfg.INSTANCE.load(pluginUrl.openStream());
            } catch (Throwable t) {LOGGER.error(t, "plugin file [{}] init failure.", pluginUrl);
            }
        }
        // 获取解析的插件汇合
        List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();

        List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
        // 循环所有插件
        for (PluginDefine pluginDefine : pluginClassList) {
            try {LOGGER.debug("loading plugin class {}.", pluginDefine.getDefineClass());
                // 创立插件实例(加载插件)AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader
                    .getDefault()).newInstance();
                plugins.add(plugin);
            } catch (Throwable t) {LOGGER.error(t, "load plugin [{}] failure.", pluginDefine.getDefineClass());
            }
        }

        // 将插件增加到内核中
        plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));
        return plugins;
    }
}

SkyWalking Agent 加载插件时应用到一个自定义的 ClassLoader —— AgentClassLoader,之所以自定义类加载器,目标是不在利用的 Classpath 中引入 SkyWalking 的插件 jar 包,这样就能够让利用无依赖、无感知的插件。

AgentClassLoader 作为一个类加载器,次要工作还是从其 Classpath 下加载类(或资源文件),对应的就是其 findClass() 办法和 findResource() 办法:

咱们来看一下 findClass,次要依据类名获取它的 Class:

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    // 扫描 classpath 所有的 jar 包
    List<Jar> allJars = getAllJars();
    // 把包替换成门路,最初加上.class
    String path = name.replace('.', '/').concat(".class");

    // 循环查找所有的 jar 包
    for (Jar jar : allJars) {
        // 加载 jar 包的信息
        JarEntry entry = jar.jarFile.getJarEntry(path);
        if (entry == null) {continue;}
        try {
            // 定位以后 jar 包地位
            URL classFileUrl = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + path);
            // 加载 jar 包
            byte[] data;
            try (final BufferedInputStream is = new BufferedInputStream(classFileUrl.openStream()); final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
                int ch;
                while ((ch = is.read()) != -1) {baos.write(ch);
                }
                data = baos.toByteArray();}
            // 返回以后对象的 Class
            return processLoadedClass(defineClass(name, data, 0, data.length));
        } catch (IOException e) {LOGGER.error(e, "find class fail.");
        }
    }
    throw new ClassNotFoundException("Can't find " + name);
}

findResource()办法次要获取文件门路,换句话了解,就是获取插件门路,咱们来看下办法:

@Override
protected URL findResource(String name) {
    // 扫描 classpath 所有的 jar 包
    List<Jar> allJars = getAllJars();
    // 循环查找所有的 jar 包
    for (Jar jar : allJars) {
        // 加载 jar 包的信息
        JarEntry entry = jar.jarFile.getJarEntry(name);
        if (entry != null) {
            try {
                // 获取 jar 包的门路
                return new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name);
            } catch (MalformedURLException ignored) {}}
    }
    return null;
}

5.3 解析插件

咱们在学习插件解析之前,先看看插件是如何定义的。咱们能够关上apm-sniffer/apm-sdk-plugin,它外面都是要用到的插件汇合:

咱们看看 mysql-5.x-plugin,在 resources(也就是 classpath) 中定义 skywalking-plugin.def 文件,在该文件中定义加载插件须要解析的类,而插件类以 key=value 模式定义,如下图:

5.3.1 PluginResourcesResolver

loadPlugins() 办法中应用了 PluginResourcesResolver,PluginResourcesResolver 是 Agent 插件的资源解析器,会通过 AgentClassLoader 中的 findResource() 办法读取所有 Agent 插件中的 skywalking-plugin.def 文件。

拿到全副插件的 skywalking-plugin.def 文件之后,PluginCfg 会逐行进行解析,转换成 PluginDefine 对象。PluginDefine 中有两个字段, 别离对应 skywalking-plugin.def 中的 key 和 value,解析流程如下:


接下来会遍历全副 PluginDefine 对象,通过反射将其中 defineClass 字段中记录的插件类实例化,外围逻辑如下:

AbstractClassEnhancePluginDefine 抽象类是所有 Agent 插件类的顶级父类,其中定义了四个外围办法,决定了一个插件类应该加强哪些指标类、应该如何加强、具体插入哪些逻辑,如下所示:

  • enhanceClass() 办法:返回的 ClassMatch,用于匹配以后插件要加强的指标类。
  • define() 办法:插件类加强逻辑的入口,底层会调用上面的 enhance() 办法和 witnessClass() 办法。
  • enhance() 办法:真正执行加强逻辑的中央。
  • witnessClass() 办法:一个开源组件可能有多个版本,插件会通过该办法辨认组件的不同版本,避免对不兼容的版本进行加强。

ClassMatch

enhanceClass() 办法决定了一个插件类要加强的指标类,返回值为 ClassMatch 类型对象。ClassMatch 相似于一个过滤器,能够通过多种形式匹配到指标类,ClassMatch 接口的实现如下:

  • NameMatch:依据其 className 字段(String 类型)匹配指标类的名称。
  • IndirectMatch:子接口中定义了两个办法。

    public interface IndirectMatch extends ClassMatch {
        //Junction 是 Byte Buddy 中的类,能够通过 and、or 等操作串联多个 ElementMatcher, 进行匹配
        ElementMatcher.Junction buildJunction();
        // 用于检测传入的类型是否匹配该 Match
        boolean isMatch(TypeDescription typeDescription);
    }
  • MultiClassNameMatch:其中会指定一个 matchClassNames 汇合,该汇合内的类即为指标类。
  • ClassAnnotationMatch:依据标注在类上的注解匹配指标类。
  • MethodAnnotationMatch:依据标注在办法上的注解匹配指标类。
  • HierarchyMatch:依据父类或是接口匹配指标类。

咱们来剖析一下 ClassAnnotationMatch 的 buildJunction()办法和 isMatch()办法:

@Override
public ElementMatcher.Junction buildJunction() {
    ElementMatcher.Junction junction = null;
    //annotations:指定了该 ClassAnnotationMatch 对象须要查看的注解
    // 遍历该对象须要查看的所有注解
    for (String annotation : annotations) {if (junction == null) {
            // 检测类是否标注了指定注解
            junction = buildEachAnnotation(annotation);
        } else {
            // 应用 and 形式将所有 Junction 对象连接起来
            junction = junction.and(buildEachAnnotation(annotation));
        }
    }
    // 排除接口
    junction = junction.and(not(isInterface()));
    return junction;
}

isMatch()办法如下:

@Override
public boolean isMatch(TypeDescription typeDescription) {List<String> annotationList = new ArrayList<String>(Arrays.asList(annotations));
    // 获取该类上的注解
    AnnotationList declaredAnnotations = typeDescription.getDeclaredAnnotations();
    // 匹配一个删除一个
    for (AnnotationDescription annotation : declaredAnnotations) {annotationList.remove(annotation.getAnnotationType().getActualName());
    }
    // 如果全副删除,则匹配胜利
    return annotationList.isEmpty();}

5.3.2 PluginFinder

PluginFinder 是 AbstractClassEnhancePluginDefine 查找器,能够依据给定的类查找用于加强的 AbstractClassEnhancePluginDefine 汇合。

在 PluginFinder 的构造函数中会遍历后面课程曾经实例化的 AbstractClassEnhancePluginDefine,并依据 enhanceClass() 办法返回的 ClassMatcher 类型进行分类,失去如下两个汇合:

// 定义了汇合
//pluginFinder 将插件分类保留在两个汇合中,别离是:按名字分类和按其余辅助信息分类
private final Map<String, LinkedList<AbstractClassEnhancePluginDefine>> nameMatchDefine = new HashMap<String, LinkedList<AbstractClassEnhancePluginDefine>>();
    private final List<AbstractClassEnhancePluginDefine> signatureMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
    private final List<AbstractClassEnhancePluginDefine> bootstrapClassMatchDefine = new ArrayList<AbstractClassEnhancePluginDefine>();
    
    // 构造方法
    public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) {for (AbstractClassEnhancePluginDefine plugin : plugins) {
            // 形象办法 enhanceClass 办法定义在插件的形象基类 AbstractClassEnhancePluginDefine 中,每一个插件必须去实现这个类中的办法
            ClassMatch match = plugin.enhanceClass();  // 故 enhanceClass 是每个插件都会本人去实现的办法,指定须要加强的类

            if (match == null) {continue;}

            if (match instanceof NameMatch) {NameMatch nameMatch = (NameMatch) match;
                LinkedList<AbstractClassEnhancePluginDefine> pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
                if (pluginDefines == null) {pluginDefines = new LinkedList<AbstractClassEnhancePluginDefine>();
                    nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
                }
                pluginDefines.add(plugin);
            } else {signatureMatchDefine.add(plugin);
            }

            if (plugin.isBootstrapInstrumentation()) {bootstrapClassMatchDefine.add(plugin);
            }
        }
    }

//typeDescription 是 bytebuddy 的内置接口,是对类的残缺形容,蕴含了类的全类名
// 传入 typeDescription,返回能够使用于 typeDescription 的类的插件
public List<AbstractClassEnhancePluginDefine> find(TypeDescription typeDescription) {List<AbstractClassEnhancePluginDefine> matchedPlugins = new LinkedList<AbstractClassEnhancePluginDefine>();
        String typeName = typeDescription.getTypeName();
        // 依据名字信息匹配查找
        if (nameMatchDefine.containsKey(typeName)) {matchedPlugins.addAll(nameMatchDefine.get(typeName));
        }
        // 通过除了名字之外的辅助信息,在 signatureMatchDefine 汇合中查找
        for (AbstractClassEnhancePluginDefine pluginDefine : signatureMatchDefine) {IndirectMatch match = (IndirectMatch) pluginDefine.enhanceClass();
            if (match.isMatch(typeDescription)) {matchedPlugins.add(pluginDefine);
            }
        }

        return matchedPlugins;
    }

public ElementMatcher<? super TypeDescription> buildMatch() {
           // 设置匹配的规定,名字是否雷同,通过名字间接匹配
        ElementMatcher.Junction judge = new AbstractJunction<NamedElement>() {
            @Override
            public boolean matches(NamedElement target) {return nameMatchDefine.containsKey(target.getActualName());
            }
        };
        judge = judge.and(not(isInterface())); // 接口不加强,排除掉
        // 如果无奈确定类的全限定名,则通过注解、回调信息等辅助办法间接匹配
        for (AbstractClassEnhancePluginDefine define : signatureMatchDefine) {ClassMatch match = define.enhanceClass();
            if (match instanceof IndirectMatch) {judge = judge.or(((IndirectMatch) match).buildJunction());
            }
        }
        return new ProtectiveShieldMatcher(judge);
}

5.3.3 AgentBuilder

利用 bytebuddy 的 API 生成一个代理,并执行 transform 办法和监听器 Listener(次要是日志相干)。

在 premain 中,通过链式调用,被 builderMatch()匹配到的类都会执行 transform 办法,transform 定义了字节码加强的逻辑:

// 应用 ByteBuddy 创立 AgentBuilder
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));

Config.Agent.IS_OPEN_DEBUGGING_CLASS 在 agent.config 中对应配置agent.is_open_debugging_class

如果将其配置为 true,则会将动静生成的类输入到 debugging 目录中。

AgentBuilder 是 Byte Buddy 库专门用来反对 Java Agent 的一个 API,如下所示:

new AgentBuilder.Default(byteBuddy) // 设置应用的 ByteBuddy 对象
.ignore(nameStartsWith("net.bytebuddy.")// 不会拦挡下列包中的类
       .or(nameStartsWith("org.slf4j."))
       .or(nameStartsWith("org.apache.logging."))
       .or(nameStartsWith("org.groovy."))
       .or(nameContains("javassist"))
       .or(nameContains(".asm."))
       .or(nameStartsWith("sun.reflect"))
       .or(allSkyWalkingAgentExcludeToolkit()) // 解决 Skywalking 的类
       // synthetic 类和办法是由编译器生成的,这品种也须要疏忽
       .or(ElementMatchers.<TypeDescription>isSynthetic()))
.type(pluginFinder.buildMatch())// 拦挡
.transform(new Transformer(pluginFinder)) // 设置 Transform
.with(new Listener()) // 设置 Listener
.installOn(instrumentation)

下面代码中有些办法咱们须要了解一下:

  • 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 实现加强,外围实现如下:

public DynamicType.Builder<?> transform(DynamicType.Builder<?>builder,
    TypeDescription typeDescription, // 被拦挡的指标类
    ClassLoader classLoader,  // 加载指标类的 ClassLoader
    JavaModule module) {
    // 从 PluginFinder 中查找匹配该指标类的插件,PluginFinder 的查找逻辑不再反复
    List<AbstractClassEnhancePluginDefine> pluginDefines =
           pluginFinder.find(typeDescription);
    if (pluginDefines.size() >0){ 
        DynamicType.Builder<?>newBuilder = builder;
        EnhanceContext context = new EnhanceContext();
        for (AbstractClassEnhancePluginDefinedefine : pluginDefines) {// AbstractClassEnhancePluginDefine.define()办法是插件入口,// 在其中实现了对指标类的加强
            DynamicType.Builder<?>possibleNewBuilder = 
                 define.define(typeDescription, 
                      newBuilder, classLoader,context);
            if (possibleNewBuilder != null) {
                // 留神这里,如果匹配了多个插件,会被加强屡次
                newBuilder = possibleNewBuilder;
            }
        }
        return newBuilder;
    }
    return builder;
}

思考:

1: 如何自定义 Skywalking 插件

2: 如何应用插件

本文由传智教育博学谷 – 狂野架构师教研团队公布,转载请注明出处!

如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源

正文完
 0