線上項目遇到問題無法調試,線下又無法重現,難道只能加日誌再重新發布麼?有了這款神器,既可以線上調試,又可以實現熱修復,推薦給大家!
Arthas 簡介
Arthas是Alibaba開源的Java診斷工具,深受開發者喜愛。它採用命令行交互模式,同時提供豐富的 Tab 自動補全功能,進一步方便進行問題的定位和診斷。
安裝
爲了還原一個真實的線上環境,我們將通過Arthas來對Docker容器中的Java程序進行診斷。
- 使用arthas-boot,下載對應jar包,下載地址:https://alibaba.github.io/arthas/arthas-boot.jar
- 將我們的Spring Boot應用mall-tiny-arthas使用Docker容器的方式啓動起來,打包和運行腳本在項目的src\main\docker目錄下;
- 將arthas-boot.jar拷貝到我們應用容器的\目錄下;
docker container cp arthas-boot.jar mall-tiny-arthas:/
- 進入容器並啓動arthas-boot,直接當做jar包啓動即可;
docker exec -it mall-tiny-arthas /bin/bash java -jar arthas-boot.jar
- 啓動成功後,選擇當前需要診斷的Java程序的序列號,這裏是1,就可以開始診斷了;
- 期間會下載一些所需的文件,完成後控制檯打印信息如下,至此Arthas就安裝啓動完成了。
常用命令
我們先來介紹一些Arthas的常用命令,會結合實際應用來講解,帶大家瞭解下Arthas的使用。
dashboard
使用dashboard命令可以顯示當前系統的實時數據面板,包括線程信息、JVM內存信息及JVM運行時參數。
thread
查看當前線程信息,查看線程的堆棧,可以找出當前最佔CPU的線程。
常用命令:
# 打印當前最忙的3個線程的堆棧信息 thread -n 3 # 查看ID爲1都線程的堆棧信息 thread 1 # 找出當前阻塞其他線程的線程 thread -b # 查看指定狀態的線程 thread -state WAITING
sysprop
查看當前JVM的系統屬性,比如當容器時區與宿主機不一致時,可以使用如下命令查看時區信息。
sysprop |grep timezone
user.timezone Asia/Shanghai
sysenv
查看JVM的環境屬性,比如查看下我們當前啓用的是什麼環境的Spring Boot配置。
logger
使用logger命令可以查看日誌信息,並改變日誌級別,這個命令非常有用。
比如我們在生產環境上一般是不會打印DEBUG級別的日誌的,當我們在線上排查問題時可以臨時開啓DEBUG級別的日誌,幫助我們排查問題,下面介紹下如何操作。
- 我們的應用默認使用的是INFO級別的日誌,使用logger命令可以查看;
- 使用如下命令改變日誌級別爲DEBUG,需要使用-c參數指定類加載器的HASH值;
logger -c 21b8d17c --name ROOT --level debug
- 再使用logger命令查看,發現ROOT級別日誌已經更改;
- 使用docker logs -f mall-tiny-arthas命令查看容器日誌,發現已經打印了DEBUG級別的日誌;
- 查看完日誌以後記得要把日誌級別再調回INFO級別。
logger -c 21b8d17c --name ROOT --level info
sc
查看JVM已加載的類信息,Search-Class的簡寫,搜索出所有已經加載到 JVM 中的類信息。
- 搜索com.macro.mall包下所有的類;
sc com.macro.mall.*
- 打印類的詳細信息,加入-d參數並指定全限定類名;
sc -d com.macro.mall.tiny.common.api.CommonResult
- 打印出類的Field信息,使用-f參數。
sc -d -f com.macro.mall.tiny.common.api.CommonResult
sm
查看已加載類的方法信息,Search-Method的簡寫,搜索出所有已經加載的類的方法信息。
- 查看類中的所有方法;
sm com.macro.mall.tiny.common.api.CommonResult
- 查看指定方法信息,使用-d參數並指定方法名稱;
sm -d com.macro.mall.tiny.common.api.CommonResult getCode
jad
反編譯已加載類的源碼,覺得線上代碼和預期不一致,可以反編譯看看。
- 查看啓動類的相關信息,默認會帶有ClassLoader信息;
jad com.macro.mall.tiny.MallTinyApplication
- 使用--source-only參數可以只打印類信息。
jad --source-only com.macro.mall.tiny.MallTinyApplication
mc
內存編譯器,Memory Compiler的縮寫,編譯.java文件生成.class。
redefine
加載外部的.class文件,覆蓋掉 JVM中已經加載的類。
monitor
實時監控方法執行信息,可以查看方法執行成功此時、失敗次數、平均耗時等信息。
monitor -c 5 com.macro.mall.tiny.controller.PmsBrandController listBrand
watch
方法執行數據觀測,可以觀察方法執行過程中的參數和返回值。
使用如下命令觀察方法執行參數和返回值,-x表示結果屬性遍歷深度。
watch com.macro.mall.tiny.service.impl.PmsBrandServiceImpl listBrand "{params,returnObj}" -x 2
熱更新
儘管在線上環境熱更代碼並不是一個很好的行爲,但有的時候我們真的很需要熱更代碼。下面介紹下如何使用jad/mc/redefine來熱更新代碼。
- 首先我們有一個商品詳情的接口,當我們傳入id<=0時,會拋出IllegalArgumentException;
/** * 品牌管理Controller * Created by macro on 2019/4/19. */ @Api(tags = "PmsBrandController", description = "商品品牌管理") @Controller @RequestMapping("/brand") public class PmsBrandController { @Autowired private PmsBrandService brandService; private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class); @ApiOperation("獲取指定id的品牌詳情") @RequestMapping(value = "/{id}", method = RequestMethod.GET) @ResponseBody public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) { if(id<=0){ throw new IllegalArgumentException("id not excepted id:"+id); } return CommonResult.success(brandService.getBrand(id)); } }
- 調用接口會返回如下信息,調用地址:http://192.168.5.94:8088/brand/0
{ "timestamp": "2020-06-12T06:20:20.951+0000", "status": 500, "error": "Internal Server Error", "message": "id not excepted id:0", "path": "/brand/0" }
- 我們想對該問題進行修復,如果傳入id<=0時,直接返回空數據的CommonResult,代碼修改內容如下;
/** * 品牌管理Controller * Created by macro on 2019/4/19. */ @Api(tags = "PmsBrandController", description = "商品品牌管理") @Controller @RequestMapping("/brand") public class PmsBrandController { @Autowired private PmsBrandService brandService; private static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class); @ApiOperation("獲取指定id的品牌詳情") @RequestMapping(value = "/{id}", method = RequestMethod.GET) @ResponseBody public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) { if(id<=0){ // throw new IllegalArgumentException("id not excepted id:"+id); return CommonResult.success(null); } return CommonResult.success(brandService.getBrand(id)); } }
- 首先我們需要對PmsBrandController類代碼進行修改,接着上傳到服務器,然後使用如下命令將java文件拷貝到容器的/tmp目錄下;
docker container cp /tmp/PmsBrandController.java mall-tiny-arthas:/tmp/
- 之後我們需要查看該類的類加載器的Hash值;
sc -d *PmsBrandController | grep classLoaderHash
- 之後使用內存編譯器把改.java文件編譯成.class文件,注意需要使用-c指定類加載器;
mc -c 21b8d17c /tmp/PmsBrandController.java -d /tmp
- 最後使用redefine命令加載.class文件,將原來加載的類覆蓋掉;
redefine -c 21b8d17c /tmp/com/macro/mall/tiny/controller/PmsBrandController.class
- 我們再次調用接口進行測試,發現已經返回了預期的結果,調用地址:http://192.168.3.101:8088/brand/0
{ "code": 200, "message": "操作成功", "data": null }
項目源碼地址:關注我私信回覆【666】或取
推薦閱讀:
牛皮了,馬士兵老師全網首播阿里P8級技術、實現大型淘寶實戰落
面試美團被JVM慘虐?阿里P9架構師用500分鐘把JVM從入門講到實戰#合集
清華啓蒙架構師馬士兵針對應屆生到開發十年的Java程序員做職業把脈