SkyWalking 的 agent 使用了一种称为字节码增强(bytecode instrumentation)的技术来实现代码增强、日志输出以及调用链路的获取。这种技术可以在程序运行时动态地修改类的字节码,插入特定的逻辑,例如记录方法的调用、参数和返回值等。
字节码增强技术
SkyWalking 主要利用了以下几种字节码增强技术:
- Java Agent
- Byte Buddy
- ASM
1. Java Agent
Java Agent 是一种允许在 JVM 启动时或运行时动态加载和修改字节码的机制。SkyWalking 使用 Java Agent 来插入其自身的字节码增强逻辑。
- Premain 和 Agentmain 方法 :Java Agent 使用
premain
方法在 JVM 启动时执行,或者使用agentmain
方法在 JVM 运行时动态加载。
java
import java.lang.instrument.Instrumentation;
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("Agent is running");
// 可以在这里添加字节码增强逻辑
}
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("Agent is dynamically loaded");
// 可以在这里添加字节码增强逻辑
}
}
2. Byte Buddy
Byte Buddy 是一个强大的字节码操作库,SkyWalking 使用 Byte Buddy 来简化字节码操作。Byte Buddy 提供了高级的 API 来生成、修改和操作字节码。
- 示例:通过 Byte Buddy 创建一个代理类,插入方法拦截逻辑。
java
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;
import java.lang.instrument.Instrumentation;
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
.type(ElementMatchers.nameContains("MyService"))
.transform((builder, typeDescription, classLoader, javaModule) ->
builder.visit(Advice.to(MyServiceAdvice.class).on(ElementMatchers.any()))
).installOn(inst);
}
}
class MyServiceAdvice {
@Advice.OnMethodEnter
static void onEnter() {
System.out.println("Before method execution");
}
@Advice.OnMethodExit
static void onExit() {
System.out.println("After method execution");
}
}
3. ASM
ASM 是一个低级的字节码操作库,可以直接操作 JVM 字节码。SkyWalking 使用 ASM 来实现更细粒度的字节码操作。
- 示例:使用 ASM 来修改字节码,在方法调用前后插入日志。
java
import org.objectweb.asm.*;
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new MyMethodVisitor(mv);
}
class MyMethodVisitor extends MethodVisitor {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
@Override
public void visitCode() {
super.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Before method execution");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.RETURN) {
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("After method execution");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
}
调用链路的获取
通过上述字节码增强技术,SkyWalking 在每个需要监控的方法前后插入代码,以捕获方法调用、参数、返回值和异常等信息。这些信息被用来生成分布式调用链路(Trace),并将其发送到 SkyWalking 的后端进行存储和分析。
- 进入方法时:记录当前时间戳、方法名、类名、参数等信息,并生成一个 Trace Segment。
- 方法执行完毕后:记录结束时间戳、返回值或异常信息,完成一个 Trace Segment。
- 分布式追踪:通过唯一的 Trace ID 和 Segment ID 将多个 Trace Segment 关联起来,形成完整的调用链路。
这些技术和方法结合使用,使得 SkyWalking 能够高效地收集和分析分布式系统的性能数据,并提供可视化的调用链路分析工具。