Apache SkyWalking 是一个开源的应用性能监控 (APM) 工具,支持分布式系统的追踪、监控和诊断。SkyWalking Agent 是其中的一个重要组件,用于在服务端应用中收集性能数据和追踪信息,并将其发送到 SkyWalking 后端服务器进行处理和展示。
SkyWalking Agent 的工作原理
-
启动时加载 Agent :
SkyWalking Agent 通过 Java Agent 机制,在 JVM 启动时加载。用户需要在启动应用时添加
-javaagent
参数,指定 SkyWalking Agent 的 JAR 文件。java -javaagent:/path/to/skywalking-agent.jar -jar your-application.jar
-
字节码增强 :
SkyWalking Agent 使用字节码增强技术(基于字节码操作库如 ASM)来修改应用程序的类文件,以插入监控代码。这些代码会在方法调用、数据库访问、HTTP 请求等关键点收集性能数据。
-
拦截方法调用 :
通过字节码增强,Agent 拦截应用程序中的特定方法调用(如 HTTP 请求、数据库查询等)。在方法开始前、方法结束后和异常抛出时,Agent 会记录相关信息。
-
收集性能数据和追踪信息 :
拦截的方法调用会生成追踪数据,这些数据包括:
- 方法执行的开始时间和结束时间
- 方法执行的耗时
- 调用链上下文信息(如 Trace ID、Span ID)
- 方法的输入输出参数和异常信息
-
数据缓冲和发送 :
收集到的数据会暂时存储在 Agent 的内存中,并定期批量发送到 SkyWalking 后端服务器。为了减少对应用程序性能的影响,数据发送通常是异步进行的。
-
后端处理和展示 :
SkyWalking 后端服务器接收到数据后,会对其进行处理、存储,并在 Web UI 中展示。用户可以通过 Web UI 查看服务的调用链、性能指标、错误信息等。
SkyWalking Agent 的关键组件
- Agent Core:负责 Agent 的初始化、配置加载和生命周期管理。
- Plugin System:SkyWalking Agent 提供了插件系统,支持不同类型的 middleware、framework 的插件,如 HTTP、Dubbo、Spring、MyBatis 等。这些插件定义了如何拦截特定的框架方法,收集性能数据。
- Tracing Context:管理调用链上下文,包括 Trace ID 和 Span ID 的生成和传递。
- Reporter:负责将收集到的数据发送到 SkyWalking 后端服务器。
自己实现一个java agent
Java Agent 是 Java Instrumentation API 的一个强大功能,它允许你在运行时修改 Java 应用程序的行为。它们通常用于性能分析、监控和修改应用程序的执行。
自己实现一个 实现一个 Java Agent 来监控 JDBC SQL 执行时间
步骤 1:创建 Java Agent
- 创建 Agent 类:
该类将实现premain
方法来进行类的插桩。
java
import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class SqlTimingAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("SQL Timing Agent loaded");
inst.addTransformer(new SqlTimingTransformer());
}
}
- 创建 Transformer 类:
该类将对java.sql.Statement
和java.sql.PreparedStatement
的execute*
方法进行插桩。
java
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
import java.lang.instrument.IllegalClassFormatException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
public class SqlTimingTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("java/sql/Statement") || className.equals("java/sql/PreparedStatement")) {
return transformSQLClass(className, classfileBuffer);
}
return classfileBuffer;
}
private byte[] transformSQLClass(String className, byte[] classfileBuffer) {
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(className.replace("/", "."));
for (CtMethod m : cc.getDeclaredMethods()) {
if (m.getName().startsWith("execute")) {
addTiming(m);
}
}
return cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return classfileBuffer;
}
private void addTiming(CtMethod method) throws Exception {
method.addLocalVariable("startTime", CtClass.longType);
method.insertBefore("startTime = System.currentTimeMillis();");
method.insertAfter("System.out.println(\"SQL Execute Time: \" + (System.currentTimeMillis() - startTime) + \" ms\");");
}
}
步骤 2:创建 Manifest 文件
创建一个 MANIFEST.MF
文件来指定代理类。
java
Manifest-Version: 1.0
Premain-Class: SqlTimingAgent
步骤 3:将 Agent 打包为 JAR
编译你的代理类和 Transformer 类,并将它们与 manifest 文件一起打包到 JAR 文件中。
java
javac SqlTimingAgent.java SqlTimingTransformer.java
jar cfm SqlTimingAgent.jar MANIFEST.MF SqlTimingAgent.class SqlTimingTransformer.class
步骤 4:使用 Java Agent
在启动 Java 应用程序时使用 -javaagent
选项指定代理 JAR 文件。
java
java -javaagent:SqlTimingAgent.jar -jar YourApplication.jar
示例解释
-
SqlTimingAgent
类:premain
方法在 JVM 启动时被调用,添加SqlTimingTransformer
作为类文件转换器。
-
SqlTimingTransformer
类:- 该类实现了
ClassFileTransformer
接口,对java.sql.Statement
和java.sql.PreparedStatement
类进行转换。 - 在
transform
方法中,检查类名是否是Statement
或PreparedStatement
,并调用transformSQLClass
方法。 transformSQLClass
方法使用 Javassist 库修改类的字节码,对execute*
方法添加时间记录代码。
- 该类实现了
-
字节码修改:
- 在
execute*
方法开始前记录当前时间。 - 在方法结束后,计算并输出 SQL 执行时间。
- 在
通过这些步骤,你可以创建和使用 Java Agent 来监控 JDBC SQL 执行时间。