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

需求背景

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

学会Java Agent你能做什么?

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

使用方法

依赖maven

   <dependency>
		   	<groupId>com.uc.agent</groupId>
			<artifactId>neighbour-agent-elasticsearch-starter</artifactId>
			<version>0.0.56</version>
	</dependency>

到此我们的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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
elasticsearch-5.2.2客户端JAVA开发要的69个jar包。当然也可以通过maven自动下载。activation-1.1.jar,bcpkix-jdk15on-1.55.jar,bcprov-jdk15on-1.55.jar,commons-codec-1.10.jar,commons-logging-1.2.jar,compiler-0.9.3.jar,elasticsearch-5.2.2.jar,elasticsearch-analysis-ik-5.2.2.jar,elasticsearch-analysis-pinyin-5.2.2.jar,guava-16.0.1.jar,HdrHistogram-2.1.6.jar,hppc-0.7.1.jar,httpasyncclient-4.1.2.jar,httpclient-4.5.2.jar,httpcore-4.4.5.jar,httpcore-nio-4.4.5.jar,jackson-core-2.8.6.jar,jackson-dataformat-cbor-2.8.6.jar,jackson-dataformat-smile-2.8.6.jar,jackson-dataformat-yaml-2.8.6.jar,javax.mail-1.5.3.jar,jna-4.2.2.jar,joda-time-2.9.5.jar,jopt-simple-5.0.2.jar,jts-1.13.jar,lang-mustache-client-5.1.1.jar,log4j-1.2-api-2.7.jar,log4j-api-2.7.jar,log4j-core-2.7.jar,lucene-analyzers-common-6.4.1.jar,lucene-backward-codecs-6.4.1.jar,lucene-core-6.4.1.jar,lucene-grouping-6.4.1.jar,lucene-highlighter-6.4.1.jar,lucene-join-6.4.1.jar,lucene-memory-6.4.1.jar,lucene-misc-6.4.1.jar,lucene-queries-6.4.1.jar,lucene-queryparser-6.4.1.jar,lucene-sandbox-6.4.1.jar,lucene-spatial-6.4.1.jar,lucene-spatial-extras-6.4.1.jar,lucene-spatial3d-6.4.1.jar,lucene-suggest-6.4.1.jar,netty-3.10.6.Final.jar,netty-buffer-4.1.7.Final.jar,netty-codec-4.1.7.Final.jar,netty-codec-http-4.1.7.Final.jar,netty-common-4.1.7.Final.jar,netty-handler-4.1.7.Final.jar,netty-resolver-4.1.7.Final.jar,netty-transport-4.1.7.Final.jar,nlp-lang-1.7.jar,owasp-java-html-sanitizer-r239.jar,percolator-client-5.1.1.jar,reindex-client-5.1.1.jar,rest-5.1.1.jar,rest-5.2.2.jar,securesm-1.1.jar,snakeyaml-1.15.jar,sniffer-5.2.2.jar,spatial4j-0.6.jar,t-digest-3.0.jar,transport-5.1.1.jar,transport-netty3-client-5.1.1.jar,transport-netty4-5.2.2.jar,transport-netty4-client-5.1.1.jar,unboundid-ldapsdk-3.2.0.jar,x-pack-5.2.2.jar,

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值