前情提要
目前项目使用的agent 是在skywalking 上进行改造扩展的,但由于skywalking 的版本也是需要周期性更新的,这样会导致在合并的时候非常恶心人,所以准备从skywalking agent 中将改造的插件功能剥离出来。
先说下独立功能有几个组件,
大概访问流程是这样的 ui --> proxy --> agent
- ui 负责命令交互,数据展示等
- proxy 负责通道维护,数据分拣
- agent 负责应用数据监控抓取
所以在我们剥离出来后也会有三个项目 ui、proxy、agent,由此大家可以看出了,基本上大部分apm 大概都是这样的流程。
此次说明的重点不是三个项目的实现,而是通过maven 进行工程的初始化构造。平时我们开发的项目无非是打出一个jar 或者war来进行项目部署。
但因为我们这一套apm 是一个整体,为了方便维护迭代,我们将其进行整合打包,先看下完成后整体的结构
先大概说明下目录的用途
- agent-dist 打包后存放agent 的目录
- agent-proxy-dist 打包后存放proxy的目录
- arthas-dis 因为我们agent 支持arthas 的所有命令集,所有需要arthas的依赖
- config agent的配置文件
- cubic-agent agent 源码
- cubic-core agent core 源码
- cubic-proxy 代理源码
- cubic-ui ui 源码
- scripts 打包脚本
其实整个项目没有多么负责,整个核心的点只有几个:
- 构建一个基础agent 项目
- 通过maven 插件maven-shade-plugin 进行打包 和 package名称的修改 ,用来防止和应用jar 冲突
- 通过 maven maven-antrun-plugin 进行部署包的整理
下面我们就来讲解下整个流程
构建
1、父项目
首先需要一个父项目来包含管理整个项目组,如果你使用IDEA 右键可以很简单的构建一个maven 项目,删除掉其src 目录后我们来看下pom 的依赖,很简单不多说了
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>matrix.cubic</groupId>
<artifactId>cubic</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cubic-agent</module>
<module>cubic-core</module>
<module>cubic-proxy</module>
</modules>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>matrix.cubic</groupId>
<artifactId>cubic-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.45.Final</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2、构建基础agent
当前agent 我分为了两个简单的包,cubic-core 包含了所有的核心代码,cubic-agent 只是包含了一个启动类,你可以根据自己需求创建。
其实打包的核心都在pom ,代码编写反而不是重点了。
来看下cubic-core的 pom
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>cubic</artifactId>
<groupId>matrix.cubic</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cubic-core</artifactId>
<properties>
<shade.package>com.matrix.cubic.agent.core.dependencies</shade.package>
<shade.com.google.source>com.google</shade.com.google.source>
<shade.com.google.target>${shade.package}.${shade.com.google.source}</shade.com.google.target>
<shade.io.grpc.source>io.grpc</shade.io.grpc.source>
<shade.io.grpc.target>${shade.package}.${shade.io.grpc.source}</shade.io.grpc.target>
<shade.io.netty.source>io.netty</shade.io.netty.source>
<shade.io.netty.target>${shade.package}.${shade.io.netty.source}</shade.io.netty.target>
<shade.org.apache.commons.source>org.apache.commons</shade.org.apache.commons.source>
<shade.org.apache.commons.target>${shade.package}.${shade.org.apache.commons.source}</shade.org.apache.commons.target>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>com.sun</groupId>
<artifactId>linux-tools</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>net.bytebuddy:byte-buddy:jar:</exclude>
<exclude>com.google.errorprone:error_prone_annotations:jar:</exclude>
<exclude>com.google.code.findbugs:jsr305:jar:</exclude>
<exclude>com.google.android:annotations:jar:</exclude>
<exclude>com.google.api.grpc:proto-google-common-protos:jar:</exclude>
<exclude>org.checkerframework:checker-compat-qual:jar:</exclude>
<exclude>org.codehaus.mojo:animal-sniffer-annotations:jar:</exclude>
</excludes>
</artifactSet>
<relocations>
<relocation>
<pattern>${shade.com.google.source}</pattern>
<shadedPattern>${shade.com.google.target}</shadedPattern>
</relocation>
<relocation>
<pattern>${shade.io.grpc.source}</pattern>
<shadedPattern>${shade.io.grpc.target}</shadedPattern>
</relocation>
<relocation>
<pattern>${shade.io.netty.source}</pattern>
<shadedPattern>${shade.io.netty.target}</shadedPattern>
</relocation>
<relocation>
<pattern>${shade.org.apache.commons.source}</pattern>
<shadedPattern>${shade.org.apache.commons.target}</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>com.google.protobuf:protobuf-java</artifact>
<excludes>
<exclude>google/protobuf/*.proto</exclude>
<exclude>google/protobuf/compiler/*.proto</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
总览全部,核心点还是maven 这几个插件,下面结合实际简单介绍下
maven-shade-plugin
这个插件就一个功能,就是帮你创建一个jar包,插件核心点有两个
- 选择内容
- 类重定位
这里的打包其实是把我们的依赖都打进去了,来看个jar 解压后的截图,比如我们依赖了slf4j,如图:
选择内容
简单来说就是我们希望哪些依赖打入jar ,哪些依赖排除。
- 标签 -》 进行配置,支持两种操作include和exclude
- 配置格式:groupId:artifactId[[:type]:classifier],至少包含groupid和artifactid,type和类名可选
- 支持’*’ 和 ‘?’执行通配符匹配
举例:
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<excludes>
<exclude>net.bytebuddy:byte-buddy:jar:</exclude>
<exclude>com.google.errorprone:error_prone_annotations:jar:</exclude>
<exclude>com.google.code.findbugs:jsr305:jar:</exclude>
<exclude>com.google.android:annotations:jar:</exclude>
<exclude>com.google.api.grpc:proto-google-common-protos:jar:</exclude>
<exclude>org.checkerframework:checker-compat-qual:jar:</exclude>
<exclude>org.codehaus.mojo:animal-sniffer-annotations:jar:</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
类的重定位
因为写agent 和组件不像写代码,agent会给很多应用使用,每个应用的情况都是不相同的,比如你引入的fastjson和对方的版本不一直导致的使用冲突,这种问题其实还是很恶心人的,也恶心了开发组。
所为为了避免这种情况,我们就可以使用此插件给我们依赖的开源包变一个package路径,这样就不再会有冲突了(因为classloader 加载一个类是通过package +class name确定唯一的)
上例子:
通过例子我们可以看出,将com.google 的package 改成了com.matrix.cubic.agent.core.dependencies.com.google,有兴趣的可以自己看下编译后的class
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<relocations>
<relocation>
<pattern>com.google</pattern>
<shadedPattern>com.matrix.cubic.agent.core.dependencies.com.google</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
打包cubic-agent
通过上面的一通操作,agent-core的核心代码已经打包完毕了,前面提过在cubic-agent 里面其实是依赖了cubic-core,并且只包含了一个启动类,真要变成一个可加载的agent 还需要在cubic-agent里面进行处理下。
核心启动类:
public static void premain(String agentArgs, Instrumentation instrumentation) {
System.out.println("add agent");
CubicConfInitalizer.initConfig();
// instrumentation.addTransformer(new DefineTransformer(), true);
AgentNettyClient client = new AgentNettyClient();
client.start();
Runtime.getRuntime()
.addShutdownHook(new Thread(client::destroyAndSync, "cubic agent shutdown thread"));
}
cubic-agent pom
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>cubic</artifactId>
<groupId>matrix.cubic</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cubic-agent</artifactId>
<dependencies>
<dependency>
<groupId>matrix.cubic</groupId>
<artifactId>cubic-core</artifactId>
</dependency>
</dependencies>
<properties>
<premain.class>com.matrix.agent.MatrixAgent</premain.class>
<can.redefine.classes>true</can.redefine.classes>
<can.retransform.classes>true</can.retransform.classes>
</properties>
<build>
<finalName>cubic-agent</finalName>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>false</shadedArtifactAttached>
<createDependencyReducedPom>true</createDependencyReducedPom>
<createSourcesJar>true</createSourcesJar>
<shadeSourcesContent>true</shadeSourcesContent>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>${premain.class}</Premain-Class>
<Can-Redefine-Classes>${can.redefine.classes}</Can-Redefine-Classes>
<Can-Retransform-Classes>${can.retransform.classes}</Can-Retransform-Classes>
</manifestEntries>
</transformer>
</transformers>
<artifactSet>
<excludes>
<exclude>*:gson</exclude>
<exclude>io.grpc:*</exclude>
<exclude>io.netty:*</exclude>
<exclude>com.google.*:*</exclude>
<exclude>com.google.guava:guava</exclude>
</excludes>
</artifactSet>
<filters>
<filter>
<artifact>net.bytebuddy:byte-buddy</artifact>
<excludes>
<exclude>META-INF/versions/9/module-info.class</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>clean</id>
<phase>clean</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<echo>${project.basedir}</echo>
<delete dir="${project.basedir}/../agent-dist" />
</tasks>
</configuration>
</execution>
<execution>
<id>package</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<mkdir dir="${project.basedir}/../agent-dist" />
<copy file="${project.build.directory}/cubic-agent.jar" tofile="${project.basedir}/../agent-dist/cubic-agent.jar" overwrite="true" />
<mkdir dir="${project.basedir}/../agent-dist/config" />
<mkdir dir="${project.basedir}/../agent-dist/logs" />
<copydir src="${project.basedir}/../config" dest="${project.basedir}/../agent-dist/config" forceoverwrite="true" />
<copydir src="${project.basedir}/../arthas-dist" dest="${project.basedir}/../agent-dist/arthas" forceoverwrite="true" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
通过pom可以发现重点是 maven-shade-plugin 和maven-antrun-plugin 两个插件
maven-shade-plugin 这里用于打造一个可执行的jar ,其实就是将一些基础信息写入到jar包的MANIFEST.MF 中,
核心点如下
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>${premain.class}</Premain-Class>
<Can-Redefine-Classes>${can.redefine.classes}</Can-Redefine-Classes>
<Can-Retransform-Classes>${can.retransform.classes}</Can-Retransform-Classes>
</manifestEntries>
</transformer>
</transformers>
maven-antrun-plugin 用来做一些搬运的工作,截取一部分核心内容
- 整体的过程就是:
- 创建一个agent-dist 文件夹
- 将我们cubic-agent 项目打包后的jar 拷贝到agent-dist下
- 创建agent-dist/config 文件夹
- 创建agent-dist/logs 文件夹
- 将agent config 拷贝到agent-dist/config 下
- 拷贝arthas-dist 目录到agent-dist/arthas下
<configuration>
<tasks>
<mkdir dir="${project.basedir}/../agent-dist" />
<copy file="${project.build.directory}/cubic-agent.jar" tofile="${project.basedir}/../agent-dist/cubic-agent.jar" overwrite="true" />
<mkdir dir="${project.basedir}/../agent-dist/config" />
<mkdir dir="${project.basedir}/../agent-dist/logs" />
<copydir src="${project.basedir}/../config" dest="${project.basedir}/../agent-dist/config" forceoverwrite="true" />
<copydir src="${project.basedir}/../arthas-dist" dest="${project.basedir}/../agent-dist/arthas" forceoverwrite="true" />
</tasks>
</configuration
至此为止我们的agent 就打包完毕并且放到我们定义的地方了,后面proxy 是一个标准的spring boot 项目,使用的也是
maven-antrun-plugin进行的整合。
3、编写打包脚本
最后就是简单的写下build.sh 打包脚本就可以执行了,有兴趣的同学也可以搞下mvnw
#!/bin/bash
cd "${0%/*}"
cd ..
mvn -v
if [ $? -ne 0 ]; then
echo "command mvn not found, Install the maven before executing the script!"
exit 0;
fi
#打包agent
echo "================ starting to build cubic agent ================"
mvn clean package -Dmaven.test.skip -Denforcer.skip=true
echo "================ building cubic agent finished ================"
总结:
- 至此我们就创建了一个方便维护的agent 项目模板,剩下的就是开心的写代码了。
- 其中的关键点就是两个插件maven-shade-plugin 和 maven-antrun-plugin,有兴趣可以通读下文档,很强大的
- 其中agent 的写法到不是重点了,有兴趣的话再写一个,推荐看下skywalking agent 扩展性真不错
- 放下此文代码库 https://gitee.com/sanjiankethree/cubic.git