参考资料: java classLoader问题: https://segmentfault.com/a/1190000040364396
java agent基础原理:https://blog.csdn.net/ancinsdn/article/details/58276945?ivk_sa=1024320u
使用 javaagent 和 动态 Attach两种方式实现类的动态修改和增强: https://www.cnblogs.com/756623607-zhang/p/12575509.html
javaagent学习笔记: https://segmentfault.com/a/1190000016601560?utm_source=tag-newest
参考项目 : 基于bytebuddy的agent简单实现:https://gitee.com/yanghuijava/agent-tutorial
http-plugin: https://github.com/alibaba/jvm-sandbox-repeater
完整版设计参考: https://github.com/apache/skywalking.git
https://gitee.com/beetle082/bee-apm.git
agent和attach模式
agent项目: https://github.com/jakubhalun/tt2016_byte_buddy_agent_demo.git
应用案例: https://github.com/jakubhalun/tt2016_byte_buddy_agent_demo_instrumented_app.git
attach命令: https://github.com/jakubhalun/tt2016_byte_buddy_agent_loader.git
可以使用agent和attach两种模式。
classloader相关的地方: 类的继承、代码中使用class或者使用class初始化对象、对于传入的已经生成的对象无影响。
1 项目介绍
基于ByteBuddy开发的java探针工具,提供http、dubbo等协议的流量复制功能。
支持 JavaAgent 和 Attach 两种模式使用。
参考项目:apache-skywalking、jvm-sandbox-repeater。
2 开发注意事项
- 尽量不要使用第三方jar包,常规基础包如apache.commons、guava、fastjson等可考虑使用。但必须使用maven-shade-plugin统一更改包路径,防止跟应用中的jar包冲突。
- 不能使用log4j等日志框架,自己需实现log功能。
- 字节码增强类需要打包成jar包,自定义classLoader来加载jar包,否则无法加载
3 使用方式
-
JavaAgent 模式
java -javaagent:<agentJarPath>[=<agentOptions>] [-D<agentKey>=<agentValue>...] -jar <projectJarPath>
example: java -javaagent:agent-1.0.0-SNAPSHOT.jar -Dtraffic.agent.http.sample.rate=100 -Dtraffic.agent.http.ignore.ips=127.0.0.1,123.*.1.1 -jar springMvc-example.jar
-
Attach 模式
java -jar <attachJarPath> <agentJarPath> <pid> [<agentOptions>]
example: java -jar attacher-1.0.0-SNAPSHOT.jar agent-1.0.0-SNAPSHOT.jar 3280 "traffic.agent.http.sample.rate=2;traffic.agent.http.ignore.ips=127.0.0.1,123.*.1.1"
4 系统参数
traffic.agent.logging.dir | 日志文件路径 | logs/apicollection |
traffic.agent.http.sample.rate | HTTP采样率,代表每N个请求采样1次 | 100 |
traffic.agent.http.ignore.ips | HTTP忽略的ip,格式为:127.0.0.1, 127.*, 127..12.22 | 无 |
traffic.agent.http.response.bytes.max | HTTP响应体数据最大值限制,超过该值则不记录ResponseBody | 204800 (200K) |
traffic.agent.plugins.mount | plugins默认路径 | plugins, activations |
系统参数优先级说明, Agent Options > Jvm Options > System Properties,容器变量认为是System Properties
5 支持的plugins
- http-plugin
6 性能测试
6.1 http-plugin性能测试
测试接口: get请求,返回一个字符串
测试命令: ab -n100000 -c50 url(10万请求 50并发量)
- 不使用agent,qps: 11765.16
- 使用agent,qps: 10760.62
7 遇到的问题总结
7.1 java.lang.NoClassDefFoundError
该异常的主要 原因是classloader问题,agent运行的classloader为app classloader(默认),springboot的classloader为JarLauncher(parent为app classloader)。
因为双亲委派机制,如果agent使用了JarLauncher加载的class,会报上述错误。
解决方法: 将 使用JarLauncher加载的class的class文件打包成jar包,自己新建个AgentClassLoader(parent为JarLauncher)来加载jar包 和 创建对象。
7.2 agent无法attach问题
bytebuddy有2套annotation api来处理aop逻辑,一套是implemetaion下的注解,另一套是adivce api 。
解决方法: 使用advice api, 只有这套注解是支持attach的
7.3 agent如何detach
ResettableClassFileTransformer resettable = new AgentBuilder.Default()
.disableClassFormatChanges()
//忽略classes的匹配规则(不拦截)
.ignore(IgnoreTypes.INSTANCE.get())
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new AgentListener())
//定义被拦截class的匹配规则
.type(MatchTypes.INSTANCE.get(enhanceClassDefineList)) //拦截class做增强处理
.transform(new Transformers(enhanceClassDefineList))
.installOn(inst);
//attach模式agent卸载
if (isRuntimeAttach) { ProcessWatcher.watch(AgentSpyApi.getAttacherPid())
.checkIntervalMills(1000)
.onShutdown(() -> {
//agent卸载
resettable.reset(inst, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
//重置attach相关的值
AgentSpyApi.destroy();
System.out.println("TrafficCaptureAgent stopped successfully!");
LOGGER.info("TrafficCaptureAgent stopped successfully!");
}).start();
}
resettable.reset(inst, AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
触发机制: 监听attacher进程号
7.4 advice api使用坑
绑定参数案例:
newBuilder.visit(Advice.withCustomMapping()
.bind(AInterceptorClass.class, methodsInterceptPoint.interceptorClass())
//拦截器代理
.to(MethodsInterceptorDelegator.class) //被拦截的方法匹配规则
.on(methodsInterceptPoint.methodMatcher()));
具体使用案例:
@Advice.OnMethodEnter
public static InterceptorContext enter(@Advice.This Object target,
@Advice.Origin Method method,
@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] allArguments,
@AInterceptorClass String interceptorClass) {
InterceptorContext context = new InterceptorContext(target, method, allArguments, method.getParameterTypes());
try {
MethodsInterceptor interceptor = InterceptorClassLoader.load(interceptorClass,
Thread.currentThread().getContextClassLoader());
interceptor.beforeMethod(context);
allArguments = context.getArguments();
} catch (Throwable t) {
LOGGER.error(t, "class[%s] before method[%s] intercept failure", target.getClass(), method.getName());
} return context;
}
@Advice.OnMethodExit(onThrowable = Throwable.class)
public static void exit(@Advice.Enter InterceptorContext context,
@Advice.Return(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object ret,
@Advice.Thrown Throwable thrown,
@AInterceptorClass String interceptorClass) {
try {
MethodsInterceptor interceptor = InterceptorClassLoader.load(interceptorClass,
Thread.currentThread().getContextClassLoader());
Object newRet = ret;
interceptor.afterMethod(context, newRet, thrown);
ret = newRet;
} catch (Throwable t) {
LOGGER.error(t, "class[%s] after method[%s] intercept failure", context.getObjInst().getClass(),
context.getMethod().getName());
}
}
@Advice.AllArguments(readOnly = false, typing = Assigner.Typing.DYNAMIC) Object[] allArguments
readOnly=false 代表可修改该对象;
typing = Assigner.Typing.DYNAMIC 不用指定具体类型来接收参数,每次获取到的allArguments是一份浅拷贝数据
版权声明:《 基于byteBuddy的java agent设计 》为Saber原创文章,转载请注明出处!
最后编辑:2021-10-28 09:10:32