本人阅读了 Skywalking 的大部分核心代码,也了解了相关的文献,对此深有感悟,特此借助巨人的思想自己手动用JAVA语言实现了一个 “调用链监控APM” 系统。本书采用边讲解实现原理边编写代码的方式,看本书时一定要跟着敲代码。
作者已经将过程写成一部书籍,奈何没有钱发表,如果您知道渠道可以联系本人。一定重谢。
本书涉及到的核心技术与思想
JavaAgent , ByteBuddy,SPI服务,类加载器的命名空间,增强JDK类,kafka,插件思想,切面,链路栈等等。实际上远不止这么多,差不多贯通了整个java体系。
适用人群
自己公司要实现自己的调用链的;写架构的;深入java编程的;阅读Skywalking源码的;
版权
本书是作者呕心沥血亲自编写的代码,不经同意切勿拿出去商用,否则会追究其责任。
原版PDF+源码请见:
本章涉及到的工具类也在这里面:
PDF书籍《手写调用链监控APM系统-Java版》第1章 开篇介绍-CSDN博客
第8章 插件与链路的结合:Gson插件实现
Gson是一个json解析工具,也没跨线程或进程调用,非常适合LocalSpan案例。
Gson在将json字符串反序列化成对象时,执行的类和方法信息如下:
类名:com.google.gson.Gson
方法:fromJson(第一个参数为JsonReader.class)
非JDK类库
Gson在将对象序列化成json字符串时,执行的类和方法信息如下:
类名:com.google.gson.Gson
方法:toJson (第二个参数为JsonWriter.class)
非JDK类库
在插件apm-agent-plugins 聚合模块下新建一个gson-plugin插件项目。POM文件添加gosn依赖,内容如下:
<dependencies><dependency><groupId>com.hadluo.apm</groupId><artifactId>apm-commons</artifactId><version>1.0</version><scope>compile</scope></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version><scope>provided</scope></dependency>
</dependencies>
新增插件定义类GsonFromInstrumentation:
public class GsonFromInstrumentation extends AbstractClassEnhancePluginDefine {@Overridepublic String enhanceClass() {// 拦截的类return "com.google.gson.Gson";}@Overridepublic MethodsInterceptPoint[] configMethodsInterceptPoint() {return new MethodsInterceptPoint[]{new MethodsInterceptPoint() {@Overridepublic ElementMatcher<MethodDescription> getMethodsMatcher() {// 拦截fromJson方法,且第一个参数为JsonReader类型return ElementMatchers.named("fromJson").and(ElementMatchers.takesArgument(0, JsonReader.class));}@Overridepublic String getMethodsInterceptor() {// 拦截逻辑执行的拦截器return "com.hadluo.apm.plugin.gson.GsonFromInterceptor";}@Overridepublic boolean isOverrideArgs() {return false;}}};}
}
同理新增GsonToInstrumentation。
新增插件定义配置文件hadluo-apm-plugin.def,内容:
gson-from=com.hadluo.apm.plugin.gson.GsonFromInstrumentation
gson-to=com.hadluo.apm.plugin.gson.GsonToInstrumentation
新增拦截逻辑执行的拦截器GsonFromInterceptor :
public class GsonFromInterceptor implements InstanceMethodsAroundInterceptor {@Overridepublic void beforeMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes) throws Throwable {// 创建 local spanTraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);AbstractSpan localSpan = manager.createLocalSpan("Gson/FromJson");localSpan.setComponent("GSON");int length = allArguments[0].toString().length();localSpan.setTag("length" , Integer.toString(length)) ;}@Overridepublic Object afterMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);manager.stopSpan();return ret;}@Overridepublic void handleMethodException(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);manager.activeSpan().log(t) ;}
}
GsonFromInterceptor 在beforeMethod中创建了LocalSpan,tag信息你可以自己定义,我这里就随便取了长度。在异常时采集log到span里面,在afterMethod中stopSpan。
同理我们可以编写出GsonToInterceptor,代码如下:
public class GsonToInterceptor implements InstanceMethodsAroundInterceptor {@Overridepublic void beforeMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes) throws Throwable {// 创建 local spanTraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);AbstractSpan localSpan = manager.createLocalSpan("Gson/ToJson");localSpan.setComponent("GSON");int length = allArguments[0].toString().length();localSpan.setTag("length" , Integer.toString(length)) ;}@Overridepublic Object afterMethod(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Object ret) throws Throwable {TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);manager.stopSpan();return ret;}@Overridepublic void handleMethodException(Object objInst, Method method, Object[] allArguments, Class<?>[] argumentsTypes, Throwable t) {TraceContextManager manager = ServiceManager.INSTANCE.getService(TraceContextManager.class);manager.activeSpan().log(t) ;}
}
到此gson插件代码编写完成,根据四部曲完善其它配置,我就不讲解了。
修改测试controller代码,在接口中进行json操作:
@GetMapping("/order")
public String order(@RequestParam("shopId")String shopId) throws ClassNotFoundException {System.out.println("下单请求 商品ID:" + shopId);Gson gson = new Gson();String json = gson.toJson(shopId);gson.fromJson(json, String.class);return UUID.randomUUID().toString();
}
打包测试,结果
{"msgTypeClass": "com.hadluo.apm.commons.kafka.Segment","sampleTime": 1733369232204,"serviceName": null,"serviceInstance": "bc640bee591447c2869b1c66ef2907be@192.168.2.233","traceId": "495c2b8bbf49424baa796b43726d256a.44.17333692321780001","traceSegmentId": "495c2b8bbf49424baa796b43726d256a.44.17333692321780000","spans": [{"spanId": 1,"parentSpanId": 0,"startTime": 0,"endTime": 1733369232195,"refs": [],"operationName": "Gson/ToJson","peer": null,"spanType": "Local","spanLayer": null,"component": "GSON","tags": {"length": "2"},"logs": {}},{"spanId": 2,"parentSpanId": 0,"startTime": 0,"endTime": 1733369232195,"refs": [],"operationName": "Gson/FromJson","peer": null,"spanType": "Local","spanLayer": null,"component": "GSON","tags": {},"logs": {}},{"spanId": 0,"parentSpanId": -1,"startTime": 1733369232179,"endTime": 1733369232204,"refs": [],"operationName": "/order","peer": null,"spanType": "Entry","spanLayer": "HTTP","component": "Tomcat","tags": {"http.method": "GET","url": "/order"},"logs": {}}]
}
结果分析:
由于tomcat插件还存在,所以第一个span为EntrySpan,然后是toJson的LocalSpan,最后是fromJson的LocalSpan 。 分析上述json数据也是如此。