本人阅读了 Skywalking 的大部分核心代码,也了解了相关的文献,对此深有感悟,特此借助巨人的思想自己手动用JAVA语言实现了一个 “调用链监控APM” 系统。本书采用边讲解实现原理边编写代码的方式,看本书时一定要跟着敲代码。
作者已经将过程写成一部书籍,奈何没有钱发表,如果您知道渠道可以联系本人。一定重谢。
本书涉及到的核心技术与思想
JavaAgent , ByteBuddy,SPI服务,类加载器的命名空间,增强JDK类,kafka,插件思想,切面,链路栈等等。实际上远不止这么多,差不多贯通了整个java体系。
适用人群
自己公司要实现自己的调用链的;写架构的;深入java编程的;阅读Skywalking源码的;
版权
本书是作者呕心沥血亲自编写的代码,不经同意切勿拿出去商用,否则会追究其责任。
原版PDF+源码请见:
本章涉及到的工具类也在这里面:
PDF书籍《手写调用链监控APM系统-Java版》第1章 开篇介绍-CSDN博客
第9章 插件与链路的结合:Mysql插件实现
9.1 Mysql插件的流程分析
数据库归根结底就是JDBC的操作,在学习时期,我们肯定会学习基本的jdbc查询数据库的写法:
// 1. 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2. 获取连接 对象
ConnectionImpl connection = (ConnectionImpl) DriverManager.getConnection("", "", "");
// 3. 准备 statement
PreparedStatement statement = connection.prepareStatement("select * from t_user");
// 4. 执行sql
statement.executeQuery();
无论用什么框架,数据库操作都避免不了上面原始步骤。我们分析下上面的流程:
1. 加载驱动,我们不关心。
2. 通过DriverManager的静态方法getConnection获取到ConnectionImpl 连接对象。
3. 通ConnectionImpl的prepareStatement方法,去配合sql准备一个PreparedStatement 。
4. 最后通过PreparedStatement的executeQuery方法去查询sql语句并返回结果。
数据库插件要想采集到调用的sql信息,就必须要拦截ConnectionImpl类的prepareStatement方法。 如果还要采集sql调用的返回信息,还需要拦截 PreparedStatement 类的executeQuery方法。但是我们还需要数据库服务器的地址信息,这个还必须要拦截 DriverManager.getConnection 。
拦截的三个类我们梳理出来了,但是这里有个很严重的问题,当我们拦截DriverManager.getConnection获取到数据库地址后,没办法向后传递到ConnectionImpl类的prepareStatement中。
我们架设一个猜想:
拦截DriverManager.getConnection返回的是ConnectionImpl。如果能在这个阶段将数据库的地址信息设置到返回的ConnectionImpl对象中,后面拦截ConnectionImpl的prepareStatement方法时,方法切面那里是不是有个参数能获取到当前对象,也就能拿到DriverManager.getConnection拦截时的数据库信息了。
这也就需要在增强类中添加Object字段,用于参数传递的思想。
我们目前的字节码增强代码时无法实现上述思想的,需要进行改造。接下来我们来讲解下如何实现。
9.2 插桩类改造,新增Object字段和实现EnhancedInstance接口
由于篇幅过长,请到第一章查看原版PDF和源码:
PDF书籍《手写调用链监控APM系统-Java版》第1章 开篇介绍-CSDN博客
9.3 Mysql插件真正实现
根据前面的分析,我们依次需要拦截
类名: java.sql.DriverManager
方法:getConnection
JDK类库
然后拦截:
类名: com.mysql.jdbc.ConnectionImpl
方法:prepareStatement
非JDK类库
最后拦截:
类名: com.mysql.jdbc.PreparedStatement
方法:executeQuery
非JDK类库
在插件模块下新增apm-mysql-plugin项目,POM内容:
<dependency><groupId>com.hadluo.apm</groupId><artifactId>apm-commons</artifactId><version>1.0</version>
</dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.44</version><scope>provided</scope>
</dependency>
hadluo-apm-plugin.def 插件定义文件:
mysql5-DriverConnect=com.hadluo.apm.plugin.mysql5.DriverConnectInstrumentationmysql5-PrepareStatement=com.hadluo.apm.plugin.mysql5.PrepareStatementInstrumentationmysql5-PrepareStatementExecute=com.hadluo.apm.plugin.mysql5.PrepareStatementExecuteInstrumentation
三个类我就不建了,值得注意的是,DriverManager#getConnection方法最终调用下面方法:
DriverConnectInstrumentation配置拦截类名时,还需要指定参数签名:
还有就是isBootstrapInstrumentation 一定要返回true, 因为它是rt.jar的JDK类。
三个方法环绕执行器分别是:
DriverConnectInterceptor
PrepareStatementInterceptor
PrepareStatementExecuteInterceptor
DriverConnectInterceptor 代码实现:
由于篇幅过长,请到第一章查看原版PDF和源码:
PDF书籍《手写调用链监控APM系统-Java版》第1章 开篇介绍-CSDN博客
打包测试,kafka数据json如下:
{"msgTypeClass": "com.hadluo.apm.commons.kafka.Segment","sampleTime": 1734056030540,"serviceName": null,"serviceInstance": "1a91d6d937ea4d6b8c2cb34dc75bf240@192.168.2.125","traceId": "c133c183325b48fdbd3c94eca8bf341e.44.17340560303730001","traceSegmentId": "c133c183325b48fdbd3c94eca8bf341e.44.17340560303710000","spans": [{"spanId": 1,"parentSpanId": 0,"startTime": 1734056030528,"endTime": 1734056030529,"refs": [],"operationName": "jdbc:mysql://127.0.0.1:3306/test/select * from t_user/executeQuery","peer": null,"spanType": "Exit","spanLayer": "DB","component": "MySQL","tags": {"remotePeer": "jdbc:mysql://127.0.0.1:3306/test","extra": "{password=, user=root}","sql": "select * from t_user"},"logs": {}},{"spanId": 0,"parentSpanId": -1,"startTime": 1734056030373,"endTime": 1734056030538,"refs": [],"operationName": "/order","peer": null,"spanType": "Entry","spanLayer": "HTTP","component": "Tomcat","tags": {"http.method": "GET","url": "/order"},"logs": {}}]
}
上述json我想应该很熟悉了,不用我过多分析,只是还有一个问题,就是serviceName为空。 熟悉SkyWalking的读者都应该知道,应用名称是在启动参数上通过 agent name 来配置的。
作者不这样做,我们可以拦截SpringBoot的启动流程,在解析到Enviroment后,将 spring.application.name 的值设置到Config,
当然,这仅限制于springboot服务生效。下节我们就来分析如何拦截springboot的Enviroment。