需求背景
agent是什么大家應(yīng)該很熟悉了,今天我們來實戰(zhàn)下,效果就是為項目所有elasticsearch請求方法增加耗時告警!
學(xué)會Java Agent你能做什么?
- 自動添加getter/setter方法的工具lombok就使用了這一技術(shù)
- btrace、Arthas和housemd等動態(tài)診斷工具也是用了instrument技術(shù)
- Intellij idea 的 HotSwap、Jrebel 等也是該技術(shù)的實現(xiàn)之一
- pinpoint、skywalking、newrelic、聽云的 APM 產(chǎn)品等都基于 Instrumentation 實現(xiàn)
使用方法
依賴maven
<dependency>
<groupId>com.uc.agent</groupId>
<artifactId>neighbour-agent-elasticsearch-starter</artifactId>
<version>0.0.56</version>
</dependency>
到此我們的agent就已經(jīng)集成了,不需要加任何啟動參數(shù),完全是無侵入式!?。。?/h4>
解決了jar -jar方式啟動的問題:
- springboot自定義類加載器LaunchedURLClassLoader ,與agent的類加載器不同的沖突問題。
- VirtualMachine綁定agent時,loadAgent方法找不到agentjar問題。
- AgentLoader 加載之前 (agent動態(tài)綁定之前) 被JVM加載過的class是不會回調(diào)addTransformer方法的。 springboot擴展點和import方式導(dǎo)入的組件class優(yōu)先AgentLoader 加載了,所以會造成agent攔截不到。
- springboot本地可以,打包到線上jar啟動方式agent無效等問題。
當(dāng)es執(zhí)行 search方法時,會自動打印方法耗時:
neighbour-agent-elasticsearch-starter 的下載地址 在github上面
https://github.com/HadLuo/neighbour-agent-elasticsearch-starter.git文章來源地址http://www.zghlxwxcb.cn/news/detail-595122.html
下面我們看下簡單的agent使用,但是沒有解決上面 jar啟動的問題,要了解實現(xiàn)請下載源碼?。。?/h4>
開場實例:
比如我們業(yè)務(wù)代碼的網(wǎng)絡(luò)請求框架代碼(模擬):
public class HttpClient {
public void post() {
System.out.println("HttpClient pos 請求");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我們要實現(xiàn)的就是監(jiān)聽當(dāng)網(wǎng)絡(luò)請求超過1秒就釘釘告警出來。比如:
實現(xiàn)過程
AgentLoader
我們先實現(xiàn)一個AgentLoader 用來加載agent:
@Configuration
public class AgentLoader implements InitializingBean{
@Override
public void afterPropertiesSet() throws Exception {
// 動態(tài)獲取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();
}
}
}
當(dāng)前放到SpringBoot初始化 Import 這個類 加載就行。
agent-client-0.0.1-SNAPSHOT-jar-with-dependencies.jar 為下面要制作的 agent jar名稱,需要放到項目的resource目錄下。
這里我們其實就是用到了agent的動態(tài)綁定方式去綁定。
agent jar制作:
agentmain方法:
JDK 1.6 引入了新的 agentmain 用于支持在類加載后再次加載該類,也就是重定義類,在重定義的時候可以修改類。但是這種方式對類的修改有較大的限制,修改后的類要兼容原來的舊類,具體的要求在 Java 官方文檔 Instrumentation#retransformClasses()方法介紹 中可以找到: 轉(zhuǎn)換類時禁止添加、刪除、重命名成員變量和方法,禁止修改方法的簽名,禁止改變類的繼承關(guān)系。
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
?都會被回調(diào)執(zhí)行。 - retransformClasses()會讓類重新加載,從而使得注冊的類修改器能夠重新修改類的字節(jié)碼。
在利用javaassit進行字節(jié)碼修改,達到了增加耗時告警目的。
到此我們實例已經(jīng)制作完畢。
但是上面會有一個問題,在線上 我們用jar -jar 啟動時,會有各種問題, 但是在文章的前面實現(xiàn)的案例都已經(jīng)解決了,需要讀者自行下載。
下載地址在github上面文章來源:http://www.zghlxwxcb.cn/news/detail-595122.html
https://github.com/HadLuo/neighbour-agent-elasticsearch-starter.git
到了這里,關(guān)于java agent 實戰(zhàn) 監(jiān)控Elasticsearch(只需依賴一個jar 完全無侵入式)解決jar啟動問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!