javascript
利用神器BTrace 追踪线上 Spring Boot应用运行时信息
可用于追蹤線上 Java服務(wù) 運(yùn)行時信息的神器 BTrace,你們經(jīng)常用嗎 ?
概述
生產(chǎn)環(huán)境中的服務(wù)可能會出現(xiàn)各種問題,但總不能讓服務(wù)下線來專門排查錯誤,這時候最好有一些手段來獲取程序運(yùn)行時信息,比如 接口方法參數(shù)/返回值、外部調(diào)用情況 以及 函數(shù)執(zhí)行時間等信息以便定位問題。傳統(tǒng)的日志記錄方式的確可以,但有時非常麻煩,甚至可能需要重啟服務(wù),因此代價太大,這時可以借助一個牛批的工具:BTrace !
BTrace 可用于動態(tài)跟蹤正在運(yùn)行的 Java程序,其原理是通過動態(tài)地檢測目標(biāo)應(yīng)用程序的類并注入跟蹤代碼 ( “字節(jié)碼跟蹤” ),因此可以直接用于監(jiān)控和追蹤線上問題而無需修改業(yè)務(wù)代碼并重啟應(yīng)用程序。
BTrace 的使用方式是用戶自己編寫符合 BTrace使用語法的腳本,并結(jié)合btrace命令,來獲取應(yīng)用的一切調(diào)用信息,就像下面這樣:
<btrace>/bin/btrace <PID> <trace_script>- 其中 <PID>為被監(jiān)控 Java應(yīng)用的 進(jìn)程ID
- <trace_script> 為 根據(jù)需要監(jiān)控的信息 而自行編寫的 Java腳本
本文就來實(shí)操一波 BTrace工具的使用,實(shí)驗(yàn)環(huán)境如下:
- OS:CentOS 7.4 64bit
- BTrace版本:1.3.11.3
- 被追蹤的 Java應(yīng)用:Spring Boot 2.1.1 應(yīng)用,這里使用我的文章《Spring Boot應(yīng)用緩存實(shí)踐之:Ehcache加持》一文中的 Spring Boot工程
BTrace 安裝部署
- 下載 二進(jìn)制文件并解壓
這里我解壓到目錄:/home/btrace
- 配置系統(tǒng)環(huán)境變量
- 驗(yàn)證 BTrace安裝情況
編譯 BTrace源碼
- 克隆源碼
- 編譯源碼
編譯源碼
- 構(gòu)建完成的生成物路徑位于:build/libs目錄下
[
我們?nèi)〕鰳?gòu)建生成的 jar包供下文使用。
利用btrace追蹤 Spring Boot應(yīng)用例析
首先我們得構(gòu)造一個 Spring Boot的模擬業(yè)務(wù) 用于下文被追蹤和分析,這里我就使用文章 《Spring Boot應(yīng)用緩存實(shí)踐之:Ehcache加持》中的實(shí)驗(yàn)工程。
我們在此工程里再添加一個 scripts包,用于放置 btrace 腳本文件:
[
由于 btrace腳本中需要用到 btrace相關(guān)的組件和函數(shù)庫,因此我們還需要在工程的 pom.xml中引入 btrace的依賴,所使用的 jar包就是上文編譯生成的 btrace-1.3.11.3.jar
<dependency><groupId>com.sun.btrace</groupId><artifactId>btrace</artifactId><version>1.3.11.3</version> </dependency>Talk is cheap ,Show you the code !接下來就用四五個實(shí)驗(yàn)來說明一切吧:
0x01 監(jiān)控方法耗時情況
btrace 腳本:
@BTrace public class BtraceTest2 {@OnMethod(clazz = "cn.codesheep.springbt_brace.controller.UserController", method = "getUsersByName", location = @Location(Kind.RETURN))public static void getFuncRunTime( @ProbeMethodName String pmn, @Duration long duration) {println( "接口 " + pmn + strcat("的執(zhí)行時間(ms)為: ", str(duration / 1000000)) ); //單位是納秒,要轉(zhuǎn)為毫秒} }接下來開始運(yùn)行 btrace腳本來攔截方法的參數(shù),首先我們用 jps命令取到需要被監(jiān)控的 Spring Boot應(yīng)用的進(jìn)程 Id為 27887,然后執(zhí)行:
/home/btrace/bin/btrace 27887 BtraceTest2.java這里我總共對 /getusersbyname接口發(fā)出了 12次 POST請求,情況如下:
12次請求情況
接下來我們再看看利用btrace腳本監(jiān)控到的 /getuserbyname接口的執(zhí)行時間:
12次請求所對應(yīng)的接口調(diào)用時間
這樣一對比很明顯,從數(shù)據(jù)庫取數(shù)據(jù)還是需要 花費(fèi)十幾毫秒的,但從緩存讀取數(shù)據(jù) 幾乎沒有耗時,這就是為什么要讓緩存加持于應(yīng)用的原因!!!
0x02 攔截方法的 參數(shù)/返回值
btrace 腳本:
@OnMethod(clazz = "cn.codesheep.springbt_brace.controller.UserController",method = "getUsersByName",location = @Location(Kind.ENTRY) ) public static void getFuncEntry(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user ) {println("類名: " + pcn);println("方法名: " + pmn);// 先打印入?yún)?shí)體整體信息BTraceUtils.print("入?yún)?shí)體為: ");BTraceUtils.printFields(user);// 再打印入?yún)?shí)體每個屬性的信息Field oneFiled = BTraceUtils.field("cn.codesheep.springbt_brace.entity.User", "userName");println("userName字段為: " + BTraceUtils.get(oneFiled, user));oneFiled = BTraceUtils.field("cn.codesheep.springbt_brace.entity.User", "userAge");println("userAge字段為: " + BTraceUtils.get(oneFiled, user));}接下來開始運(yùn)行 btrace腳本來攔截方法的參數(shù),首先我們用 jps命令取到需要被監(jiān)控的java應(yīng)用的進(jìn)程 Id為 27887,然后執(zhí)行:
/home/btrace/bin/btrace -cp springbt_brace/target/classes 27887 BtraceTest4.java此時正常帶參數(shù) {"userName":"codesheep.cn"} 去請求業(yè)務(wù)接口:POST /getusersbyname,會得到如下輸出:
成功攔截到了接口入?yún)?/p>
很明顯請求參數(shù)已經(jīng)被 btrace給攔截到了
同理,如果想攔截方法的返回值,可以使用如下 btrace腳本:
@OnMethod(clazz = "cn.codesheep.springbt_brace.controller.UserController",method = "getUsersByName",location = @Location(Kind.RETURN) //函數(shù)返回的時候執(zhí)行,如果不填,則在函數(shù)開始的時候執(zhí)行 ) public static void getFuncReturn( @Return List<User> users ) {println("返回值為: ");println(str(users)); }運(yùn)行 btrace命令后,繼續(xù)請求想要被監(jiān)控的業(yè)務(wù)接口,則可以得到類似如下的輸出:
成功攔截到了接口返回值
0x03 監(jiān)控代碼是否到達(dá)了某類的某一行
btrace 腳本如下:
@BTrace public class BtraceTest3 {@OnMethod(clazz="cn.codesheep.springbt_brace.service.UserService",method="getUsersByName",location=@Location(value= Kind.LINE, line=28) // 比如攔截第28行, 28行是從數(shù)據(jù)庫取數(shù)據(jù)操作)public static void lineTest( @ProbeClassName String pcn, @ProbeMethodName String pmn, int line ) {BTraceUtils.println("ClassName: " + pcn);BTraceUtils.println("MethodName: " + pmn);BTraceUtils.println("執(zhí)行到的line行數(shù): " + line);} }執(zhí)行 btrace追蹤命令
/home/btrace/bin/btrace 28927 BtraceTest3.java接著用 POSTMAN工具連續(xù)發(fā)出了對 /getuserbyname接口的 十幾次POST請求,由于只有第一次請求沒有緩存時才會從數(shù)據(jù)庫讀,因此也才會執(zhí)行到 UserService類的第 28行 !
0x04 監(jiān)控指定函數(shù)中所有外部調(diào)用的耗時情況
btrace腳本如下:
@BTrace public class BtraceTest5 {@OnMethod (clazz = "cn.codesheep.springbt_brace.service.UserService",method = "getUsersByName",location=@Location(value= Kind.CALL, clazz="/.*/", method="/.*/", where = Where.AFTER) )public static void printMethodRunTime(@Self Object self,@TargetInstance Object instance,@TargetMethodOrField String method, @Duration long duration) {if( duration > 5000000 ){ //如果外部調(diào)用耗時大于 5ms 則打印出來println( "self: " + self );println( "instance: " + instance );println( method + ",cost:" + duration/1000000 + " ms" );}}}執(zhí)行監(jiān)控命令:
/home/btrace/bin/btrace 28927 BtraceTest5.java然后再對接口 /getuserbyname發(fā)出POST請求,觀察監(jiān)控結(jié)果如下:
[](https://raw.githubusercontent.com/hansonwang99/pic/master/springbt-btrace/發(fā)現(xiàn)最耗時的外部調(diào)用來源于 MyBatis調(diào)用.png)發(fā)現(xiàn)最耗時的外部調(diào)用來源于 MyBatis調(diào)用
我們發(fā)現(xiàn)最耗時的外部調(diào)用來源于 MyBatis調(diào)用。
0x05 其他追蹤與監(jiān)控
除了上面四種典型的追蹤場景之外,其他的 btrace追蹤與監(jiān)控場景還比如 查看誰調(diào)用了System.gc(),調(diào)用棧如何,則可以使用如下 btrace腳本進(jìn)行監(jiān)控
@BTrace public class BtraceTest {@OnMethod(clazz = "java.lang.System", method = "gc")public static void onSystemGC() {println("entered System.gc()");jstack();} }很明顯,因?yàn)閎trace 內(nèi)置了一系列諸如 jstack等十分有用的監(jiān)控命令。
當(dāng)然最后需要說明的是 btrace內(nèi)置了很多語法和命令,可以應(yīng)對很多線上 Java應(yīng)用監(jiān)控場景,大家可以去研究一下官方文檔
后記
由于能力有限,若有錯誤或者不當(dāng)之處,還請大家批評指正,一起學(xué)習(xí)交流!
本文轉(zhuǎn)載自https://www.codesheep.cn/2019/01/17/springbt-btrace/
如有侵權(quán),請聯(lián)系作者刪除
總結(jié)
以上是生活随笔為你收集整理的利用神器BTrace 追踪线上 Spring Boot应用运行时信息的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用MyBatis Generator进
- 下一篇: 【工具类】手动获取被spring管理的b