使用 docker 容器部署項目已經成爲開發者必須掌握的技能,當使用 docker 容器部署項目後,如何在容器中對 Java 應用進行實時診斷,這篇文章主要介紹在 docker 容器中如何使用 Java 診斷工具 —— Arthas 。在容器中使用 Arthas 和在服務器上面使用是沒有太大區別的,一般狀況下一個容器中只會運行咱們的應用服務這一個 Java 進程,因此在容器中使用 Arthas 只會看到一個 Java 進程。關於 Arthas 的詳細說明能夠查看下面的官方文檔,這裏只會對本身在 docker 容器中使用過的 Arthas 命令經過案例進行介紹。java
這篇文章涉及的內容以下:linux
Arthas 中文文檔: http://arthas.gitee.io/
這裏使用一個 spring boot 的 demo 進行實踐,其中只包括一個 controller, 內容以下:git
@Slf4j @RestController public class TestController { @GetMapping("hello/{content}") public String hello(@PathVariable(value = "content") String content) { log.debug("----------log debug----------"); log.info("----------log info----------"); log.warn("----------log warn----------"); log.error("----------log error----------"); return "返回結果:" + content; } }
構建鏡像的 Dockerfile 內容以下:web
FROM openjdk:8u232-jdk WORKDIR /app LABEL maintainer="peterwd" app="devops-demo" COPY target/devops-demo.jar devops-demo.jar EXPOSE 8080 CMD java -jar devops-demo.jar
使用以下命令構建鏡像:spring
docker build -t devops-demo .
使用下面的命令啓動容器:docker
docker run --name devop-demo -d -p 8080:8080 devops-demo
構建好鏡像以後使用以下命令進入 docker 容器:vim
docker exec -it devops-demo bash
進入 docker 容器以後,使用以下命令安裝 Arthas:bash
wget https://arthas.aliyun.com/arthas-boot.jar
使用以下命令啓動 Arthas:服務器
java -jar arthas-boot.jar
啓動 Arthas 的過程當中會選擇對應的 Java 進程,在 docker 容器中一般只有一個 Java 進程,因此直接 1 便可,若是有多個 Java 進程輸入前面的編號。
以下圖所示:session
請注意,這些命令,都經過字節碼加強技術來實現的,會在指定類的方法中插入一些切面來實現數據統計和觀測,所以在線上、預發使用時,請儘可能明確須要觀測的類、方法以及條件,診斷結束要執行 stop 或將加強過的類執行 reset 命令。
help——查看命令幫助信息
cat——打印文件內容,和linux裏的cat命令相似
echo–打印參數,和linux裏的echo命令相似
grep——匹配查找,和linux裏的grep命令相似
base64——base64編碼轉換,和linux裏的base64命令相似
tee——複製標準輸入到標準輸出和指定的文件,和linux裏的tee命令相似
pwd——返回當前的工做目錄,和linux命令相似
cls——清空當前屏幕區域
session——查看當前會話的信息
reset——重置加強類,將被 Arthas 加強過的類所有還原,Arthas 服務端關閉時會重置全部加強過的類
version——輸出當前目標 Java 進程所加載的 Arthas 版本號
history——打印命令歷史
quit——退出當前 Arthas 客戶端,其餘 Arthas 客戶端不受影響
stop——關閉 Arthas 服務端,全部 Arthas 客戶端所有退出
keymap——Arthas快捷鍵列表及自定義快捷鍵
dashboard——當前系統的實時數據面板
thread——查看當前 JVM 的線程堆棧信息
jvm——查看當前 JVM 的信息
sysprop——查看和修改JVM的系統屬性
sysenv——查看JVM的環境變量
vmoption——查看和修改JVM裏診斷相關的option
perfcounter——查看當前 JVM 的Perf Counter信息
logger——查看和修改logger
getstatic——查看類的靜態屬性
ognl——執行ognl表達式
mbean——查看 Mbean 的信息
heapdump——dump java heap, 相似jmap命令的heap dump功能
vmtool——從jvm裏查詢對象,執行forceGc
sc——查看JVM已加載的類信息
sm——查看已加載類的方法信息
jad——反編譯指定已加載類的源碼
mc——內存編譯器,內存編譯.java文件爲.class文件
retransform——加載外部的.class文件,retransform到JVM裏
redefine——加載外部的.class文件,redefine到JVM裏
dump——dump 已加載類的 byte code 到特定目錄
classloader——查看classloader的繼承樹,urls,類加載信息,使用classloader去getResource
monitor——方法執行監控
watch——方法執行數據觀測
trace——方法內部調用路徑,並輸出方法路徑上的每一個節點上耗時
stack——輸出當前方法被調用的調用路徑
tt——方法執行數據的時空隧道,記錄下指定方法每次調用的入參和返回信息,並能對這些不一樣的時間下調用進行觀測
logger
實時修改類的日誌級別前面簡單介紹了 Arthas 的命令,這裏主要介紹使用 Arthas 的 logger
實時修改類的日誌級別,這裏的使用的 demo 中定義了四種日誌級別,分別是 debug、info、warn、error
,經過動態修改不一樣日誌級別來控制日誌的顯示。
log4j 定義了8個級別的log(除去 OF F和 ALL,能夠說分爲6個級別),優先級從高到低依次爲:
OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。若是將log level設置在某一個級別上,那麼比此級別優先級高的log都能打印出來。例如,若是設置優先級爲WARN,那麼OFF、FATAL、ERROR、WARN 4個級別的log能正常輸出,而INFO、DEBUG、TRACE、 ALL級別的log則會被忽略。Log4j建議只使用四個級別,優先級從高到低分別是
ERROR、WARN、INFO、DEBUG
。
sc
命令查看 JVM 加載的類信息命令:sc -d [查找類的全路徑 或者 *類名]
sc
命令支持經過類名模糊查找類信息,-d
顯示詳細信息,獲取到類的全路徑名和 classLoaderHash
以下所示:
[arthas@7]$ sc -d *TestController class-info devops.demo.controller.TestController code-source file:/app/devops-demo.jar!/BOOT-INF/classes!/ name devops.demo.controller.TestController isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name TestController modifier public annotation org.springframework.web.bind.annotation.RestController interfaces super-class +-java.lang.Object class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@7daf6ecc +-sun.misc.Launcher$AppClassLoader@70dea4e +-sun.misc.Launcher$ExtClassLoader@1a04f701 classLoaderHash 7daf6ecc
logger
命令查看指定類的日誌級別命令:logger --name [查找類的全路徑]
logger
用來查看和修改logger信息,--name
指定全路徑類名,以下所示:
[arthas@7]$ logger --name devops.demo.controller.TestController name devops.demo.controller.TestController class ch.qos.logback.classic.Logger classLoader org.springframework.boot.loader.LaunchedURLClassLoader@7daf6ecc classLoaderHash 7daf6ecc level null effectiveLevel INFO additivity true codeSource jar:file:/app/devops-demo.jar!/BOOT-INF/lib/logback-classic-1.2.3.jar!/
logger
命令修改指定類的日誌級別命令:logger -c [classLoaderHash的值] --name [查找類的全路徑] --level [待更新的日誌level]
-c
指定 classLoaderHash 的值 --level
指定要更新的日誌級別,以下所示:
[arthas@7]$ logger -c 7daf6ecc --name devops.demo.controller.TestController --level debug Update logger level success.
默認狀況下類的日誌級別是 info
,這裏訪問 demo 輸出日誌沒有 debug 的信息,以下圖所示:
使用以下命令修改 logger 的日誌級別爲 debug:
[arthas@7]$ logger -c 7daf6ecc --name devops.demo.controller.TestController --level debug Update logger level success.
再次訪問,輸出日誌有 debug 信息,以下圖所示:
使用以下命令修改 logger 的日誌級別爲 error:
[arthas@7]$ logger -c 7daf6ecc --name devops.demo.controller.TestController --level error Update logger level success.
再次訪問,輸出日誌只有 error 信息,以下圖所示:
watch
查看方法輸入輸出參數命令: watch 全路徑類名 方法名 [表達式]
watch
命令的使用說明以下:watch
用來查看指定方法調用的輸入輸出參數,返回值以及拋出的異常信息,watch
可使用的表達式以下:
target : the object clazz : the object's class method : the constructor or method params : the parameters array of method params[0..n] : the element of parameters array returnObj : the returned object of method throwExp : the throw exception of method isReturn : the method ended by return isThrow : the method ended by throwing exception #cost : the execution time in ms of method invocation
關於 watch
命令的詳細說明可使用 watch --help
查看,這裏只介紹示例方法的使用。
使用以下命令查看方法的調用參數:
[arthas@7]$ watch devops.demo.controller.TestController hello params Press Q or Ctrl+C to abort. Affect(class count: 1 , method count: 1) cost in 26 ms, listenerId: 3 method=devops.demo.controller.TestController.hello location=AtExit ts=2021-05-26 11:36:58; [cost=0.627099ms] result=@Object[][ @String[測試方法調用參數], ]
使用以下命令查看方法的返回參數:
[arthas@7]$ watch devops.demo.controller.TestController hello returnObj Press Q or Ctrl+C to abort. Affect(class count: 1 , method count: 1) cost in 24 ms, listenerId: 4 method=devops.demo.controller.TestController.hello location=AtExit ts=2021-05-26 11:39:18; [cost=0.525488ms] result=@String[返回結果:測試方法返回參數]
使用 Arthas 提供的 sc jad mc redefine
這四個命令就能夠實如今線代碼熱更新,這個功能很是強大,可是也很是危險,在容器中使用要控制進入容器的權限,在服務器使用也要控制服務器的使用權限,下面以提供的 demo 爲例詳細說明如何使用這幾個命令實現代碼熱更新。
sc
命令查找 JVM 加載的類信息命令:sc -d [查找類的全路徑 或者 *類名]
sc
命令支持經過類名模糊查找類信息,-d
顯示詳細信息,獲取到類的全路徑名和 classLoaderHash
以下所示:
[arthas@7]$ sc -d *TestController class-info devops.demo.controller.TestController code-source file:/app/devops-demo.jar!/BOOT-INF/classes!/ name devops.demo.controller.TestController isInterface false isAnnotation false isEnum false isAnonymousClass false isArray false isLocalClass false isMemberClass false isPrimitive false isSynthetic false simple-name TestController modifier public annotation org.springframework.web.bind.annotation.RestController interfaces super-class +-java.lang.Object class-loader +-org.springframework.boot.loader.LaunchedURLClassLoader@7daf6ecc +-sun.misc.Launcher$AppClassLoader@70dea4e +-sun.misc.Launcher$ExtClassLoader@1a04f701 classLoaderHash 7daf6ecc
jad
命令反編譯已加載類的源碼命令:jad --source-only 類的全路徑 > 類名.java
jad
命令反編譯已加載類的源碼, --source-only
指定只輸出源碼,> 類名.java
將輸出結果保存到當前目錄的 類名.java
文件
jad --source-only devops.demo.controller.TestController > TestController.java
查看反編譯的內容以下:
[arthas@7]$ cat TestController.java /* * Decompiled with CFR. * * Could not load the following classes: * org.slf4j.Logger * org.slf4j.LoggerFactory * org.springframework.web.bind.annotation.GetMapping * org.springframework.web.bind.annotation.PathVariable * org.springframework.web.bind.annotation.RestController */ package devops.demo.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { private static final Logger log = LoggerFactory.getLogger(TestController.class); @GetMapping(value={"hello/{content}"}) public String hello(@PathVariable(value="content") String content) { /*14*/ log.debug("----------log debug----------"); /*15*/ log.info("----------log info----------"); /*16*/ log.warn("----------log warn----------"); /*17*/ log.error("----------log error----------"); return "返回結果:" + content; } }
在容器中沒有 vim 編輯器,不方便修改,能夠將反編譯出來的源碼複製出來,修改完成以後,經過 docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
命令將修改後的內容複製到容器中。
docker cp TestController.java devop-demo:/app
修改上面反編譯出來的源代碼,修改以下:
[arthas@7]$ cat TestController.java /* * Decompiled with CFR. * * Could not load the following classes: * org.slf4j.Logger * org.slf4j.LoggerFactory * org.springframework.web.bind.annotation.GetMapping * org.springframework.web.bind.annotation.PathVariable * org.springframework.web.bind.annotation.RestController */ package devops.demo.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { private static final Logger log = LoggerFactory.getLogger(TestController.class); @GetMapping(value={"hello/{content}"}) public String hello(@PathVariable(value="content") String content) { /*14*/ log.debug("----------log debug----------"); /*15*/ log.info("----------log info----------"); /*16*/ log.warn("----------log warn----------"); /*17*/ log.error("----------log error----------"); return "返回結果:測試熱更新代碼 " + content; } }
mc
內存編譯.java文件爲.class文件命令 mc -c 類加載器hash java源碼路徑 -d /tmp
mc
內存編譯.java文件爲.class文件,-c 類加載器hash
指定前面經過 sc -d
命令查找到的 classLoaderHash , -d /tmp
指定編譯輸出的 class 文件的目錄爲 /tmp
, 不指定則輸出到當前目錄。
[arthas@7]$ mc -c 7daf6ecc TestController.java -d /tmp Memory compiler output: /tmp/devops/demo/controller/TestController.class Affect(row-cnt:1) cost in 993 ms.
redefine
加載外部的.class文件命令:redefine class文件路徑
這裏的 class 文件路徑填寫上面反編譯輸出的路徑,以下所示:
[arthas@7]$ redefine /tmp/devops/demo/controller/TestController.class redefine success, size: 1, classes: devops.demo.controller.TestController
root@devops-demo-7bdf65859c-mtjqm:/app# curl localhost:8080/hello/test 返回結果:測試熱更新代碼 test
這篇文章簡單介紹了 Arthas 在 docker 容器的使用,主要介紹了 logger
和 watch
命令以及如何實如今線代碼熱更新,後續有使用到其餘命令再來補充,詳細信息能夠查閱官方文檔。