写点什么

源码解析 --skywalking agent 插件加载流程

用户头像
cloudcoder
关注
发布于: 2021 年 02 月 25 日

1. 插件

目前很多框架,都采用框架 + 插件的模式开发。

如 DataX、FlinkX 通过插件支持众多异构数据源, Skywalking 通过插件实现针对很多软件如 redis、mysql、dubbo 等方法执行信息采集。

本文针对 skywalking agent 插件加载流程进行源码解析,理解插件的接口定义、加载机制

针对 skywalking agent 的插件开发指导,请参考:https://github.com/apache/skywalking/blob/master/docs/en/guides/Java-Plugin-Development-Guide.md

1.1. 插件介绍

插件Plugin(Plug-in, addin, add-in, addon 或 add-on)是一种计算机应用程序,它和主应用程序(host application)互相交互,以提供特定的功能。

插件是一种可以热插拔的(动态安装和卸载),可以实现一定功能性并且对目前现有运行系统不会产生任何影响的一种松散耦合的设计模式,而且易扩展,可以让更多的开发者参与进来,让产品自身的功能更加丰富彩,它也可以通过动态的安装组合,实现不同的产品架构。

1.2. 插件机制

主应用程序提供给插件可以使用的服务,让插件在主应用程序中注册插件本身,以及和插件进行数据交换的协议。

插件依赖于主应用程序提供的这些服务,通常不能独立运行。相反地,主应用程序和插件是分离的,这就使得我们可以不改变主应用程序而动态增加或更新插件。

主应用程序提供一套插件接口规范及插件开发流程,允许第三方编写插件和主应用程序交互。

2. 源码解析环境

基于 skywalking 8.3.0 版本的源代码进行分析。

步骤如下:

  1. 将 skywalking 的 apm-sniffer 导入 IntelliJ IDEA 中

  2. 在 project 中新增一个 java module

  3. 在 run/debug 配置的 VM options 中增加 javaagent 参数,具体如下:

-javaagent: 具体路径\skywalking-agent.jar -Dskywalking.agent.servicename=SourceDebug -Dskywalking.agent.applicationcode=SourceDebug -Dskywalking.collector.backend_service=IP:11800 -Dfile.encoding=UTF-8

注意

  • 使用 IntelliJ IDEA 的菜单 File / New / Module 或 File / New / Module from Existing Sources ,保证 新项目和 skywalking 项目平级。这样,才可以使用 IntelliJ IDEA 调试 Agent 。详细请参考:调试环境搭建

  • 将具体路径调整为 skywalking-agent.jar 所在的目录,将 IP 替换为部署 skywalking server 的服务器 IP

3. skywalking agent 的插件加载

插件加载的时序图如下:


具体插件加载流程如下:


  1. 在 javaagent 的入口类即 SkyWalkingAgent 中,创建 PluginFinder 对象,

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;        }
复制代码
  1. 调用 PluginBootstrap 实例的 loadPlugins()加载插件

  2. loadPlugins()首先初始化 AgentClassLoader 对象,并根据 AgentPackagePath 类,找到 skywalking-agent.jar 所在的目录,即 agent 的部署目录,将 agent 目录下的 plugins、activations 加入到 classpaths 中。 注意:plugins 目录下存储所有 agent 的插件,activations 目录下存储 toolkit 插件如 log4j,logback

  3. 获取所有插件定义的路径集合,实际代码及获取的路径集合如下图

public List<URL> getResources() {        List<URL> cfgUrlPaths = new ArrayList<URL>();        Enumeration<URL> urls;        try {            urls = AgentClassLoader.getDefault().getResources("skywalking-plugin.def");
while (urls.hasMoreElements()) { URL pluginUrl = urls.nextElement(); cfgUrlPaths.add(pluginUrl); LOGGER.info("find skywalking plugin define in {}", pluginUrl); }
return cfgUrlPaths; } catch (IOException e) { LOGGER.error("read resources failure.", e); } return null; }
复制代码


  1. 循环 resources, 针对每一个插件,处理如下:

读取插件的 skywalking-plugin.def 定义

将每一行转换为一个插件定义 PluginDefine 实例,并将实例添加到 pluginClassList 中

通过 pluginSelector.select 排除配置 EXCLUDE_PLUGINS 中定义的插件

  1. 循环 pluginClassList,将每一个插件转换为 AbstractClassEnhancePluginDefine 的实例,添加到 plugins 中

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());            }        }
复制代码
  1. 通过 DynamicPluginLoader 的 load 方法,通过 SPI 的方式发现并实例化其它插件,并添加到 plugins 中

public List<AbstractClassEnhancePluginDefine> load(AgentClassLoader classLoader) {        List<AbstractClassEnhancePluginDefine> all = new ArrayList<AbstractClassEnhancePluginDefine>();        for (InstrumentationLoader instrumentationLoader : ServiceLoader.load(InstrumentationLoader.class, classLoader)) {            List<AbstractClassEnhancePluginDefine> plugins = instrumentationLoader.load(classLoader);            if (plugins != null && !plugins.isEmpty()) {                all.addAll(plugins);            }        }        return all;    }
复制代码
  1. 到此,插件加载完毕,后面的流程是 skywalking agent 构建 byteBuddy,将所有插件增强的类注册到 AgentBuilder 中,以便针对增强的类进行拦截。

4. skywalking agent 的插件定义

插件定义的类图如下,根据 skywalking 提供的插件开发指导,继承 ClassInstanceMethodsEnhancePluginDefine、ClassStaticMethodsEnhancePluginDefine 或 ClassEnhancePluginDefine,并定义对应的拦截实现即可。



  • AbstractClassEnhancePluginDefine :所有 Agent 插件的基础抽象基类,侧重于定义,将增强的类转换为 bytebuddy 的 DynamicType 实例,并定义的 enhance 抽象方法

  • ClassEnhancePluginDefine :类增强插件定义抽象类,这个类控制所有增强操作,包括增强构造函数、实例方法和静态方法,并实现的 enhance 方法。

所有增强基于如下三种类型的拦截点。

ConstructorInterceptPoint

InstanceMethodsInterceptPoint

StaticMethodsInterceptPoint

  • ClassInstanceMethodsEnhancePluginDefine : 只需要增强类的实例方法,和直接继承 ClassEnhancePluginDefine 没有区别

  • ClassStaticMethodsEnhancePluginDefine : 只需要增强类的静态方法,和直接继承 ClassEnhancePluginDefine 没有区别

  • 具体的插件根据需要,实现如下方法:

public ClassMatch enhanceClass();

public ConstructorInterceptPoint[] getConstructorsInterceptPoints()

public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints()

public StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints()

发布于: 2021 年 02 月 25 日阅读数: 33
用户头像

cloudcoder

关注

10+IT行业老兵,熟悉大数据处理,分布式编程 2020.10.30 加入

InfoQ主页:https://www.infoq.cn/u/cloudcoder 头条主页: https://www.toutiao.com/c/user/token/MS4wLjABAAAAgqsp3gTUFRwr49uODP0W6XdaDB2vI5d_9yPDC1Nzmds/

评论

发布
暂无评论
源码解析--skywalking agent插件加载流程