java agent 实战 监控Elasticsearch(只需依赖一个jar 完全无侵入式)解决jar启动问题

您所在的位置:网站首页 北京适合露营的月份有哪些 java agent 实战 监控Elasticsearch(只需依赖一个jar 完全无侵入式)解决jar启动问题

java agent 实战 监控Elasticsearch(只需依赖一个jar 完全无侵入式)解决jar启动问题

2023-07-03 11:28| 来源: 网络整理| 查看: 265

需求背景

agent是什么大家应该很熟悉了,今天我们来实战下,效果就是为项目所有elasticsearch请求方法增加耗时告警!

学会Java Agent你能做什么?

自动添加getter/setter方法的工具lombok就使用了这一技术btrace、Arthas和housemd等动态诊断工具也是用了instrument技术Intellij idea 的 HotSwap、Jrebel 等也是该技术的实现之一pinpoint、skywalking、newrelic、听云的 APM 产品等都基于 Instrumentation 实现 使用方法

依赖maven

com.uc.agent neighbour-agent-elasticsearch-starter 0.0.56 到此我们的agent就已经集成了,不需要加任何启动参数,完全是无侵入式!!!! 解决了jar -jar方式启动的问题: springboot自定义类加载器LaunchedURLClassLoader ,与agent的类加载器不同的冲突问题。VirtualMachine绑定agent时,loadAgent方法找不到agentjar问题。AgentLoader 加载之前 (agent动态绑定之前) 被JVM加载过的class是不会回调addTransformer方法的。 springboot扩展点和import方式导入的组件class优先AgentLoader 加载了,所以会造成agent拦截不到。springboot本地可以,打包到线上jar启动方式agent无效等问题。

当es执行 search方法时,会自动打印方法耗时:

neighbour-agent-elasticsearch-starter 的下载地址 在github上面

https://github.com/HadLuo/neighbour-agent-elasticsearch-starter.git

下面我们看下简单的agent使用,但是没有解决上面 jar启动的问题,要了解实现请下载源码!!! 开场实例:

比如我们业务代码的网络请求框架代码(模拟):

public class HttpClient { public void post() { System.out.println("HttpClient pos 请求"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }

我们要实现的就是监听当网络请求超过1秒就钉钉告警出来。比如:

实现过程 AgentLoader

我们先实现一个AgentLoader 用来加载agent:

@Configuration public class AgentLoader implements InitializingBean{ @Override public void afterPropertiesSet() throws Exception { // 动态获取SpringBoot启动类名称 StartAppClassName = getMainClassName(); // 加载agent jar包 得到路径 File file = FileLoads.loadFile("agent-client-0.0.1-SNAPSHOT-jar-with-dependencies.jar"); String jar = file.getAbsolutePath(); try { for (VirtualMachineDescriptor virtualMachineDescriptor : VirtualMachine.list()) { // 针对指定名称的JVM实例 if (virtualMachineDescriptor.displayName().equals(StartAppClassName)) { System.out.println( "将对该进程的vm进行增强:org.example.agent.AgentTest的vm进程, pid=" + virtualMachineDescriptor.id()); // attach到新JVM VirtualMachine vm = VirtualMachine.attach(virtualMachineDescriptor); // 加载agentmain所在的jar包 vm.loadAgent(jar); // detach vm.detach(); } } } catch (Exception e) { e.printStackTrace(); } } }

当前放到SpringBoot初始化 Import 这个类 加载就行。

agent-client-0.0.1-SNAPSHOT-jar-with-dependencies.jar 为下面要制作的 agent jar名称,需要放到项目的resource目录下。

这里我们其实就是用到了agent的动态绑定方式去绑定。

agent jar制作:

agentmain方法:

JDK 1.6 引入了新的 agentmain 用于支持在类加载后再次加载该类,也就是重定义类,在重定义的时候可以修改类。但是这种方式对类的修改有较大的限制,修改后的类要兼容原来的旧类,具体的要求在 Java 官方文档 Instrumentation#retransformClasses()方法介绍 中可以找到: 转换类时禁止添加、删除、重命名成员变量和方法,禁止修改方法的签名,禁止改变类的继承关系。

public static void agentmain(String args, Instrumentation instrumentation) { instrumentation.addTransformer(new ClassFileTransformer() { public byte[] transform(ClassLoader l, String className, Class c, ProtectionDomain pd, byte[] b) { try { if (className == null) { return null; } // System.err.println(className); className = className.replace("/", "."); if (className.equals("com.uc.riskcontroller.trace.HttpClient")) { final ClassPool classPool = ClassPool.getDefault(); final CtClass clazz = classPool.get("com.uc.riskcontroller.trace.HttpClient"); for (CtMethod method : clazz.getMethods()) { if (Modifier.isNative(method.getModifiers())) { continue; } method.addLocalVariable("s", classPool.get("long")); method.insertBefore("s = System.currentTimeMillis();"); method.insertAfter("System.out.println(System.currentTimeMillis() - s);", false); method.insertAfter("com.uc.framework.alert.AlertContext.robot(com.uc.framework.env.EnvironmentServer.UnkownExceptionWebwork).alert(\"http客户端请求耗时:\" + (System.currentTimeMillis() - s ));", false); } return clazz.toBytecode(); } } catch (Exception e) { e.printStackTrace(); } return null; } }, true); Class[] classes = instrumentation.getAllLoadedClasses(); if (classes != null) { for (Class c : classes) { if (c.isInterface() || c.isAnnotation() || c.isArray() || c.isEnum()) { continue; } if (c.getName().equals("com.uc.riskcontroller.trace.HttpClient")) { try { System.out.println("retransformClasses start, class: " + c.getName()); instrumentation.retransformClasses(c); System.out.println("retransformClasses end, class: " + c.getName()); } catch (UnmodifiableClassException e) { System.out.println("retransformClasses error, class: " + c.getName() + ", ex:" + e); e.printStackTrace(); } } } } }

核心就在于Instrumentation的两个方法:

void addTransformer(ClassFileTransformer transformer, boolean canRetransform); void retransformClasses(Class... classes) throws UnmodifiableClassException; addTransformer()用来注册类的修改器;JVM每装载一个类,transform 都会被回调执行。retransformClasses()会让类重新加载,从而使得注册的类修改器能够重新修改类的字节码。

在利用javaassit进行字节码修改,达到了增加耗时告警目的。

到此我们实例已经制作完毕。

但是上面会有一个问题,在线上 我们用jar -jar 启动时,会有各种问题, 但是在文章的前面实现的案例都已经解决了,需要读者自行下载。

下载地址在github上面

https://github.com/HadLuo/neighbour-agent-elasticsearch-starter.git



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3