PDF书籍《手写调用链监控APM系统-Java版》第9章 插件与链路的结合:Mysql插件实现

server/2024/12/27 22:42:48/

本人阅读了 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。


http://www.ppmy.cn/server/153744.html

相关文章

CTFHUB-web进阶-php

我们用蚁剑中的这个插件来做这些关卡 一.LD_PRELOAD 发现这里有一句话木马&#xff0c;并且把ant给了我们&#xff0c;我们直接连接蚁剑 右键 选择模式&#xff0c;都可以试一下&#xff0c;这里第一个就可以 点击开始 我们进入到目录&#xff0c;刷新一下&#xff0c;会有一个…

银河麒麟 SSH Vscode连接

SSH连接错误&#xff1a; [20:04:32.376] Failed to set up socket for dynamic port forward to remote port 38671: Socket closed. TCP port forwarding may be disabled, or the remote server may have crashed. See the VS Code Server log above for details. [20:04:3…

【Linux】进程间通信 -> 匿名管道命名管道

进程间通信的目的 数据传输&#xff1a;一个进程许需要将它的数据发送给另外一个进程。资源共享&#xff1a;多个进程之间共享同样的资源。通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它们发生了某种事件&#xff08;如进程终止时要通知父进程…

C语言基础:指针(数组指针与指针数组)

数组指针与指针数组 数组指针 概念&#xff1a;数组指针是指向数组的指针&#xff0c;本质上还是指针 特点&#xff1a; 先有数组&#xff0c;后有指针 它指向的是一个完整的数组 一维数组指针&#xff1a; 语法&#xff1a; 数据类型 (*指针变量名)[行容量][列容量]; 案…

彻底解决docker:docker: Get https://registry-1.docker.io/v2/: net/http: request canceled 报错

报错 1. docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers). See docker run --help. 2.Error response from daemon: G…

深入探索 ClickHouse:性能优化之道

在大数据处理的广袤天地里&#xff0c;ClickHouse 宛如一颗璀璨的明星&#xff0c;以其卓越的性能为海量数据的存储与查询提供了强大助力。但要想让 ClickHouse 发挥出极致效能&#xff0c;性能优化至关重要。今天&#xff0c;就让我们一同深入探寻 ClickHouse 的性能优化之路。…

华为管理变革之道:奋斗文化与活力

目录 企业文化是什么&#xff1f; 为什么活下去是华为的文化&#xff1f; 活下来&#xff0c;是华为公司的最低纲领&#xff0c;也是华为公司的最高纲领&#xff01; 资源终会枯竭&#xff0c;唯有文化才能生生不息 企业文化之一&#xff1a;以客户为中心 企业文化之二&a…

简述Git中如何将一个新增文件添加到本地仓库?

在Git中&#xff0c;将一个新增文件添加到本地仓库通常需要以下步骤&#xff1a; 将文件添加到暂存区&#xff1a;首先&#xff0c;你需要使用git add命令来将新文件添加到暂存区。 使用文件名&#xff1a;git add <filename>使用点号添加所有文件&#xff1a;git add .使…