阿里one java agent的可插拔java agent运行

基于one java agent的可插拔java agent方案

背景

第⼀阶段:⾃研微服务
阿⾥巴巴的微服务拆分实践进⾏的很早,从 2008 年就开始了,当时的单体应⽤已经⽆法承载业务迭代的速度,由五彩⽯项⽬开始了微服务化的改造,在这个改造过程中,也逐步诞⽣了服务框架,消息队列,数据库分库分表等三⼤中间件。在这个阶段的服务治理能⼒是通过 SDK⽅式直接依赖在框架⾥⾯的。每个中间件都有⾃⼰独⽴的 SDK 依赖,服务治理能⼒的升级需要借助框架 SDK 的升级来解决,升级成本是很⾼的。

阶段:Fat-SDK
随着中间件接⼊数量的增加,业务升级成本不断攀升,从 2013 年起诞⽣了代号 “Pandora”的项⽬,主要有 2 个⽬标,⼀是解决中间件和业务依赖的冲突问题,⼆是解决服务治理升级效率的问题。同⼀个组件,业务和中间件的可能依赖不同的版本,最常⻅的例如⽇志,序列化组件等等,如果⼤家共享⼀个版本则会出现中间件的升级影响到业务,或者出现不兼容的情况。Pandora 提供了⼀个轻量的隔离容器,通过类加载器隔离的⽅式,将中间件和业务的依赖互相隔离,⽽中间件和中间件之间的依赖也能互相隔离。另外,通过 Fat-SDK 的⽅式,将所有中间件⼀次性打包交付给业务⽅升级。这⼀点和 Maven 引⼊的 bom 的思路类似,但是相⽐bom 来说每个 Pandora 的插件都可以享有独⽴的依赖。通过这种⽅式,业务不再需要单独升级某个中间件,⽽是⼀次性把所有的中间件完成升级,从⽽⼤幅提升了中间件升级的效率。

第三阶段:One Java Agent
随着业务的进⼀步发展,中间件的数量逐步增加,Pandora 的⽅式也遇到了相当多的问题,也就是如果要把⼀个 Pandora 的版本在全集团内全部推平,需要⻓达 1 年的时间才能完成。这是因为即使是 Pandora 的⽅式,也需要业务修改代码,升级,验证,发布,这些并⾮业务真正关⼼,业务更希望专注于⾃身业务的发展。通常借助双⼗⼀⼤促这样的机会,才有可能完成中间件的升级。这也给服务治理的形态带来新的挑战。2019 年,阿⾥推出了 One Java Agent 的形态,把服务治理的能⼒下沉到 Java Agent 的形式,通过⽆侵⼊的⽅式,实现了中间件的迭代升级,进⼀步提升了升级效率。

在One Java Agent中, 各个中间件的代码能够独⽴开发、部署,且尽可能做到互不影响,其有以下几种特性:

  • 每个 plugin 可以由启动参数来单独控制是否开启。
  • 各个 plugin 的启动是并⾏的,将 java agent 的启动速度由 O(n+m+…)提升⾄O(n)。
  • 各个 plugin 的类,都由不同的类加载器加载,最⼤限度隔离了各个 plugin,解决了各个agent可能出现的依赖冲突问题。
  • 每个 plugin 的状态都可以上报到服务端,可以通过监控来检测各个 plugin 是否有问题。

在这里插入图片描述

One Java Agent 的 开源地址:https://github.com/alibaba/one-java-agent

使用

下载源码

下载源码:https://github.com/alibaba/one-java-agent,下载后的源码如下图所示

在这里插入图片描述

其中核心包为one-java-agent、one-java-agent-plugin、one-java-agent-spy,其他为示例demo工程。

开发agent插件

由于one-java-agent需要统一维护和管理插件,因此需要将需管理的agent插件加入one-java-agent工程体系中,按照接入场景分,一般为几种场景:

  • 未开发的agent接入
  • 已开发的agent jar接入
  • 已开发的agent源码接入

下面分别针对以上三中情况详细的开发说明:

未开发的agent接入

即将要开发agent可按照one-java-agent的开发规范接入one-java-agent体系,主要有以下步骤:

1**、新建子模块工程**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gTMPHi8s-1651908568239)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image005.png)]

工程文件需包括以下几部分:

**打包配置类:**assembly文件夹以及assembly.xml

**插件配置类:**plugin.properties

**agent****启动类:**DemoAgent

**插件激活器:**PluginActivator

插件所需的类加载处理器:DemoPluginClassLoaderHandler

**pom****依赖文件:**pom.xml

2**、配置打包配置类**

将plugin配置文件以及当前代码打包,一般默认即可

Expand source

<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>bin</id>
    <formats>
        <format>zip</format>
        <format>dir</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <unpack>false</unpack>
            <outputFileNameMapping>${artifactId}.jar</outputFileNameMapping>
            <includes>
                <include>${artifact}</include>
            </includes>
        </dependencySet>
        <dependencySet>
            <outputDirectory>/lib</outputDirectory>
            <unpack>false</unpack>
            <excludes>
                <exclude>${artifact}</exclude>
            </excludes>
        </dependencySet>
    </dependencySets>
    <files>
        <file>
               <source>src/main/plugin.properties</source>
            <destName>plugin.properties</destName>
        </file>
    </files>
</assembly>

3**、引入pom.xml文件依赖**

Expand source

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.alibaba.oneagent</groupId>
        <artifactId>one-java-agent-parent</artifactId>
        <version>0.0.2</version>
        <relativePath>../pom.xml</relativePath>
    </parent>
    <artifactId>demo-plugin</artifactId>
 
    <dependencies>
        <dependency>
            <groupId>com.alibaba.oneagent</groupId>
            <artifactId>one-java-agent-plugin</artifactId>
            <version>${project.version}</version>
            <optional>true</optional>
            <scope>provided</scope>
        </dependency>
 
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
 
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
 
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
            <scope>provided</scope>
        </dependency>
 
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>bytekit-core</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>demo-plugin</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <encoding>UTF-8</encoding>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
 
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <executions>
                    <execution>
                        <id>bin</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <finalName>${project.artifactId}@${project.version}</finalName>
                            <appendAssemblyId>false</appendAssemblyId>
                            <descriptors>
                                <descriptor>src/main/assembly/assembly.xml</descriptor>
                            </descriptors>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
 
        </plugins>
    </build>
 
    <profiles>
        <profile>
            <id>local</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-antrun-plugin</artifactId>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>run</goal>
                                </goals>
                                <configuration>
                                    <tasks>
                                        <delete dir="${user.home}/oneagent/plugins/${project.artifactId}@${project.version}">
                                        </delete>
                                        <copy todir="${user.home}/oneagent/plugins/${project.artifactId}@${project.version}">
                                            <fileset dir="${project.build.directory}/${project.artifactId}@${project.version}" />
                                        </copy>
                                    </tasks>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
 

4**、编写agent启动类**

根据需要在premain或agentmain写实现

Expand source

import java.lang.instrument.Instrumentation;
 
/**
 * 
 * @author hengyunabc 2020-07-28
 *
 */ 
public class DemoAgent {
 
        public static void premain(String args, Instrumentation inst) {
               init(true, args, inst);
        }
 
        public static void agentmain(String args, Instrumentation inst) {
               init(false, args, inst);
        }
 
        public static synchronized void init(boolean premain, String args, Instrumentation inst) {
               System.out.println("demo-plugin demo-agent started.");
        }
 
}

5**、编写插件激活器PluginActivator**

PluginActivator激活器需实现enabled(控制是否启动),init(初始化),start(开始),stop(结束)接口,对插件实现控制以及生命周期的监测。

Expand source

import com.alibaba.bytekit.ByteKit;
import com.alibaba.fastjson.JSON;
import com.alibaba.oneagent.plugin.PluginActivator;
import com.alibaba.oneagent.plugin.PluginContext;
 
public class DemoActivator implements PluginActivator {
    private String name = this.getClass().getSimpleName();
 
    @Override
    public boolean enabled(PluginContext context) {
        System.out.println("enabled " + this.getClass().getName());
        System.err.println(this.getClass().getSimpleName() + ": " + JSON.toJSONString(this));
 
        System.err.println("bytekit url: " + ByteKit.class.getProtectionDomain().getCodeSource().getLocation());
        return true;
    }
 
    @Override
    public void init(PluginContext context) throws Exception {
        // 注册自定义的ClassLoaderHandler,让被增强的类可以加载到指定的类
        ClassLoaderHandlerManager loaderHandlerManager = context.getComponentManager().getComponent(ClassLoaderHandlerManager.class);
        loaderHandlerManager.addHandler(new DemoPluginClassLoaderHandler());
 
           System.out.println("init " + this.getClass().getName());
               Instrumentation instrumentation = context.getInstrumentation();
        String args = context.getProperty("args");
        DemoAgent.init(true,args,instrumentation);     }
 
    @Override
    public void start(PluginContext context) throws Exception {
        System.out.println("start " + this.getClass().getName());
    }
 
    @Override
    public void stop(PluginContext context) throws Exception {
        System.out.println("stop " + this.getClass().getName());
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}

6**、编写DemoPluginClassLoaderHandler类加载处理类**:

Expand source

 
import com.alibaba.oneagent.service.ClassLoaderHandler;
 
/**
 * 
 * @author hengyunabc 2021-08-26
 *
 */
public class DemoPluginClassLoaderHandler implements ClassLoaderHandler {
 
    @Override
    public Class<?> loadClass(String name) {
        if (name.startsWith("com.activator.test")) {
            try {
                Class<?> clazz = this.getClass().getClassLoader().loadClass(name);
                return clazz;
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

7**、配置插件配置类**

Expand source

specification=1
name=demo-plugin
version=1.0.0
classpath=demo-plugin.jar:lib/
pluginActivator=com.activator.test.DemoActivator
importPackages=com.alibaba.fastjson,com.alibaba.bytekit

其中name、version、pluginActivator根据实际情况指定。

–实际例子参照代码工程中的demo-plugin子模块

已开发的agent jar接入

对于已开发好的agent jar包,同样需要新建一个插件管理,新建的配置参考上面的章节,有以下几点区别:

1**、不用写 agent启动类,因为xxx-agent.jar已经有相关代码了**

2**、需将agent.jar文件直接放入与plugin.properties同级别的目录中**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KN52upeG-1651908568240)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image007.png)]

3**、需在plugin.properties中指定jar路径,这样one-java-agent才可以启动此jar包**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oPN6m8Z2-1651908568240)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image009.png)]

4**、需在assembly.xml中指定将jar打包**

 <assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <id>bin</id>
    <formats>
        <format>zip</format>
        <format>dir</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <unpack>false</unpack>
            <outputFileNameMapping>${artifactId}.jar</outputFileNameMapping>
            <includes>
                <include>${artifact}</include>
            </includes>
        </dependencySet>
        <dependencySet>
            <outputDirectory>/lib</outputDirectory>
            <unpack>false</unpack>
            <excludes>
                <exclude>${artifact}</exclude>
            </excludes>
        </dependencySet>
    </dependencySets>
 
<!--
    使用文件组进行打包,把plugin.properties文件和xxx.jar文件(对于已经做好的javaagent JAR文件需要放入插件的根目录下)
-->
    <fileSets>
        <fileSet>
            <directory>src/main/</directory>
            <includes>
                <include>*.properties</include>
                <include>*.jar</include>
            </includes>
            <outputDirectory></outputDirectory>
        </fileSet>
    </fileSets>
</assembly>

这样即可集成进one-java-agent体系中,实际例子参照代码工程中的opentelemetry-plugin子模块

已开发好的agent源码接入

对于已开发完毕的agnet源码工程,也需跟场景一一样新建子工程,只不过跳过新的agent启动类编写,而去改造已有的agent启动类以及修改新编写的插件激活器PluginActivator。改造如下:

一般来说,一个已开发好的Agent 源码,它会有一个包含 premain 函数的启动类,假设为MyAgent类:

public class MyAgent {
    public static void premain(String args, Instrumentation inst) {
        // do something
    }
}

可以先把原来的初始化逻辑抽取为init函数,把原来的初始化逻辑移到里面,例如:

public class MyAgent {
 
    public static void premain(String args, Instrumentation inst) {
        init(args, inst);
    }
 
    public static void init(String args, Instrumentation inst) {
        // do something
    }
}

然后按上面的文档,在插件激活器PluginActivator的init函数里调用原来的MyAgent.init(args, instrumentation);函数

public class MyActivator implements PluginActivator {
...
    @Override
    public void init(PluginContext context) throws Exception {
        Instrumentation instrumentation = context.getInstrumentation();
        String args = context.getProperty("args");
        MyAgent.init(args, instrumentation);
    }
...
}

参数传递方式

对于agent插件所需的参数,一般有如下几种传递方式:

1、配置到插件的plugin.properties中,通过PluginContext#getProperty("key1")来获取值

2、通过-D参数配置,比如插件aaa,则可以配置为-Doneagent.plugin.aaa.key1=value1,然后可以通过PluginContext#getProperty("key1")来获取值。

3、配置在环境变量中,使用System.getenv(”xxx“)获取,如opentelemetry通过在启动脚本中export设置环境变量,opentelemetry agent会从环境变量中获取。

4、通过-D参数传递,然后在代码中用System.getProperty(“xxxx”)获取,如elastic apm agent则是通过-D参数传递,

打包

编译后使用mvn clean package -P local -DskipTests会打包后安装最新到本地 ~/oneoneagent 目录下如C:\Users\Administrator\oneagent:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7MdrUIB-1651908568240)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image011.png)]

其中core存放的是one-java-agent的jar包,在使用时直接使用此agent做java-agent

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2dSb4Oq-1651908568241)(file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image013.png)]

而plugins则是存放相关可插拔的插件:

在这里插入图片描述

运行

在打完包后,使用如下:

java -javaagent:C:\Users\Administrator\oneagent\core\oneagent@0.0.2\one-java-agent.jar -jar ./springboot-mybatis2-1.0-SNAPSHOT.jar

可以通过-D来指定参数,可以指定的参数如下:

  • oneagent.verbose 打印trace级别的日志,打印日志到stdout
  • oneagent.plugin.disabled 禁止指定插件启动,比如 oneagent.plugin.disabled=aaa,bbb,ccc
  • oneagent.plugin.${pluginName}.enabled 指定是否启动某个插件,比如: oneagent.plugin.aaa.enabled

Q&A

1、其他团队管理的one-java-agent打包出来的插件文件夹直接扔到统一的plugns下能用吗 。回答: 能用。

2、elastic-apm agent 使用-D的方式参数是否能传递。 回答:能

3、opentelemetry agent使用export设置环境变量是否能传递 回答:能

4、插件存在多版本的情况是否会加载最大版本 。回答: 20220428目前不会,维护者已经列出issue,待解决。

5、两个agent如果代理了同一个类,会不会起冲突?不会,各自的增强代码会不受影响低执行

6、不同agent的类能否实现共享?回答:可以,通过exportPackages,importPackages实现类共享,然后通过反射来执行共享类的方法。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值