第⼀阶段:⾃研微服务
阿⾥巴巴的微服务拆分实践进⾏的很早,从 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**、新建子模块工程**
工程文件需包括以下几部分:
**打包配置类:**assembly文件夹以及assembly.xml
**插件配置类:**plugin.properties
**agent****启动类:**DemoAgent
**插件激活器:**PluginActivator
插件所需的类加载处理器:DemoPluginClassLoaderHandler
**pom****依赖文件:**pom.xml
2**、配置打包配置类**
将plugin配置文件以及当前代码打包,一般默认即可
Expand source
bin
zip
dir
false
/
false
${artifactId}.jar
${artifact}
/lib
false
${artifact}
src/main/plugin.properties
plugin.properties
3**、引入pom.xml文件依赖**
Expand source
4.0.0 demo-plugin com.alibaba.oneagent one-java-agent-parent0.0.2 ../pom.xml com.alibaba.oneagent one-java-agent-plugin${project.version} true provided org.slf4j slf4j-apich.qos.logback logback-classicch.qos.logback logback-corecom.alibaba fastjson1.2.76 provided com.alibaba bytekit-coreprovided demo-plugin org.apache.maven.plugins maven-compiler-plugin1.6 1.6 UTF-8 true maven-assembly-plugin bin package single ${project.artifactId}@${project.version} falsesrc/main/assembly/assembly.xml local org.apache.maven.plugins maven-antrun-pluginpackage run
4**、编写agent启动类**
根据需要在premain或agentmain写实现
Expand source
import java.lang.instrument.Instrumentation;
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;
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同级别的目录中**
3**、需在plugin.properties中指定jar路径,这样one-java-agent才可以启动此jar包**
4**、需在assembly.xml中指定将jar打包**
bin
zip
dir
false
/
false
${artifactId}.jar
${artifact}
/lib
false
${artifact}
src/main/
*.properties
*.jar
这样即可集成进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:UsersAdministratoroneagent:
其中core存放的是one-java-agent的jar包,在使用时直接使用此agent做java-agent
而plugins则是存放相关可插拔的插件:
运行在打完包后,使用如下:
java -javaagent:C:UsersAdministratoroneagentcoreoneagent@0.0.2one-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
1、其他团队管理的one-java-agent打包出来的插件文件夹直接扔到统一的plugns下能用吗 。回答: 能用。
2、elastic-apm agent 使用-D的方式参数是否能传递。 回答:能
3、opentelemetry agent使用export设置环境变量是否能传递 回答:能
4、插件存在多版本的情况是否会加载最大版本 。回答: 20220428目前不会,维护者已经列出issue,待解决。



