APM - 零侵入监控Service服务
文章目錄
- 需求
- 采集方案
- 采集目標
- 模型設計
- Code
需求
通常情況下,如果我們沒有系統內部的調用情況,比如我們這里重點聚焦的Service層的接口性能指標 ,比如 調用次數、Avg執行時間、Min執行時間、Max執行時間、成功次數、失敗次數、慢執行次數等等,以及根據監控結果觸發某些告警等等 ,上述指標都是沒有辦法很靈活的采集到的
采集方案
我們先來討論下實現上述需求的方案
那如何做到更靈活的實現代碼零侵入的實現Service層的接口監控呢?
OK ,直奔主題 。方案必然是第三種,使用字節碼插樁實現Service的零侵入監控
采集目標
我們需要對哪些對象插樁呢?
@Service注解 標注的類嗎? 這里犯了一個致命的錯誤,如果想要做這種底層的基礎組件,不要對用戶的使用場景做設定 ,方案要更具有通用性
我們更傾向于讓用戶自主配置監控的 include 與 exclude .
我們不知道統計哪個類,也不知道統計哪個方法 ,一切都是基于用戶自主的配置
模型設計
核心: 使用JavaAgent獲取到用戶配置的數據, 匹配(排除)后 使用javassist來修改字節碼,進行插樁 ,插入我們的監控邏輯。
那我們都需要監控哪些指標呢?
開始時間、用時、異常消息、異常類型、服務類名、方法名 ,當然了都是可以擴展的比如我們可以增加主機IP、應用名稱、標識追蹤ID等等
簡單起見,我們先不引入過多的字段。
Code
我們先聚焦到Javassit修改字節碼,不要和JavaAgent摻和在一起,先實現第一步
public class ArtisanServiceCollect extends AbstractCollect {private String targetPackage;public ArtisanServiceCollect(String target_package) {this.targetPackage = target_package;}public void transform(Instrumentation instrumentation) {instrumentation.addTransformer(new ClassFileTransformer() {@Overridepublic byte[] transform(ClassLoader loader, String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) {if (className == null) {return null;}if (!className.startsWith(targetPackage.replaceAll("\\.", "/"))) {return null;}try {return buildCtClass(loader, className.replaceAll("/", ".")).toBytecode();} catch (Exception e) {e.printStackTrace();}return null;}});}public CtClass buildCtClass(ClassLoader loader, String className) throws NotFoundException {ClassPool pool = new ClassPool();pool.insertClassPath(new LoaderClassPath(loader));CtClass ctClass = pool.get(className);CtMethod[] methods = ctClass.getDeclaredMethods();for (CtMethod m : methods) {if (!Modifier.isPublic(m.getModifiers())) {continue;}if (Modifier.isStatic(m.getModifiers())) {continue;}if (Modifier.isNative(m.getModifiers())) {continue;}try {buildMethod(ctClass, m);} catch (Exception e) {e.printStackTrace();}}return ctClass;}public void buildMethod(CtClass ctClass, CtMethod oldMethod) throws Exception {// copy 一個方法// 修改源方法名稱 $agent// 原方法中 插入模板代碼CtMethod newMethod = CtNewMethod.copy(oldMethod, ctClass, null);oldMethod.setName(oldMethod.getName() + "$agent");String beginSrc = String.format("Object stat=com.artisan.agent.collect.ServiceCollect.begin(\"%s\",\"%s\");", ctClass.getName(), oldMethod.getName());String errorSrc = "com.artisan.agent.collect.ServiceCollect.error(e,stat);";String endSrc = "com.artisan.agent.collect.ServiceCollect.end(stat);";String template = oldMethod.getReturnType().getName().equals("void") ? voidSource : source;newMethod.setBody(String.format(template, beginSrc, newMethod.getName(), errorSrc, endSrc));ctClass.addMethod(newMethod);}public static ServiceStatistics begin(String className, String methodName) {ServiceStatistics bean = new ServiceStatistics();bean.setBeginTime(System.currentTimeMillis());bean.setServiceName(className);bean.setMethodName(methodName);bean.setModelType("service");System.out.println(JSON.toJSONString(bean));return bean;}public static void error(Throwable e, Object obj) {ServiceStatistics bean = (ServiceStatistics) obj;bean.setErrorType(e.getClass().getSimpleName());bean.setErrorMsg(e.getMessage());}public static void end(Object obj) {ServiceStatistics bean = (ServiceStatistics) obj;bean.setUseTime(System.currentTimeMillis() - bean.getBeginTime());System.out.println(JSON.toJSONString(obj));}// Object obj= begin (className,methodName)// error(err,obj)// end(obj)final static String source = "{\n"+ "%s"+ " Object result=null;\n"+ " try {\n"+ " result=($w)%s$agent($$);\n"+ " } catch (Throwable e) {\n"+ "%s"+ " throw e;\n"+ " }finally{\n"+ "%s"+ " }\n"+ " return ($r) result;\n"+ "}\n";final static String voidSource = "{\n"+ "%s"+ " try {\n"+ " %s$agent($$);\n"+ " } catch (Throwable e) {\n"+ "%s"+ " throw e;\n"+ " }finally{\n"+ "%s"+ " }\n"+ "}\n"; }寫個單元測試測試一下javassit是否生效。
當然了僅僅有這個javassit是無法直接運行的,我們還要依靠javaagent來實現對類的攔截
總結
以上是生活随笔為你收集整理的APM - 零侵入监控Service服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IDEA 启动 Tomcat 乱码 解决
- 下一篇: APM - 零侵入监控Http服务