本人閱讀了?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。