在 docker 容器中使用 Java 診斷工具 —— Arthas

1、概述

使用 docker 容器部署項目已經成爲開發者必須掌握的技能,當使用 docker 容器部署項目後,如何在容器中對 Java 應用進行實時診斷,這篇文章主要介紹在 docker 容器中如何使用 Java 診斷工具 —— Arthas 。在容器中使用 Arthas 和在服務器上面使用是沒有太大區別的,一般狀況下一個容器中只會運行咱們的應用服務這一個 Java 進程,因此在容器中使用 Arthas 只會看到一個 Java 進程。關於 Arthas 的詳細說明能夠查看下面的官方文檔,這裏只會對本身在 docker 容器中使用過的 Arthas 命令經過案例進行介紹。java

這篇文章涉及的內容以下:linux

  • 實時修改日誌級別
  • 實時查看方法調用輸入輸出參數
  • 實時在線熱更新代碼
Arthas 中文文檔: http://arthas.gitee.io/

2、在 docker 容器中安裝 Arthas

一、構建 docker 容器

這裏使用一個 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

二、安裝 Arthas

進入 docker 容器以後,使用以下命令安裝 Arthas:bash

wget https://arthas.aliyun.com/arthas-boot.jar

使用以下命令啓動 Arthas:服務器

java -jar arthas-boot.jar

啓動 Arthas 的過程當中會選擇對應的 Java 進程,在 docker 容器中一般只有一個 Java 進程,因此直接 1 便可,若是有多個 Java 進程輸入前面的編號。
以下圖所示:session

image.png

3、Arthas 命令介紹

請注意,這些命令,都經過字節碼加強技術來實現的,會在指定類的方法中插入一些切面來實現數據統計和觀測,所以在線上、預發使用時,請儘可能明確須要觀測的類、方法以及條件,診斷結束要執行 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快捷鍵列表及自定義快捷鍵

二、jvm 相關命令

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

三、class/classloader相關命令

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相關命令

monitor——方法執行監控

watch——方法執行數據觀測

trace——方法內部調用路徑,並輸出方法路徑上的每一個節點上耗時

stack——輸出當前方法被調用的調用路徑

tt——方法執行數據的時空隧道,記錄下指定方法每次調用的入參和返回信息,並能對這些不一樣的時間下調用進行觀測

3、使用 Arthas 的 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 的信息,以下圖所示:

image.png

使用以下命令修改 logger 的日誌級別爲 debug:

[arthas@7]$ logger -c 7daf6ecc --name devops.demo.controller.TestController --level debug
Update logger level success.

再次訪問,輸出日誌有 debug 信息,以下圖所示:

image.png

使用以下命令修改 logger 的日誌級別爲 error:

[arthas@7]$ logger -c 7daf6ecc --name devops.demo.controller.TestController --level error
Update logger level success.

再次訪問,輸出日誌只有 error 信息,以下圖所示:

image.png

4、使用 Arthas 的 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[返回結果:測試方法返回參數]

5、使用 Arthas 實如今線代碼熱更新

使用 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

6、總結

這篇文章簡單介紹了 Arthas 在 docker 容器的使用,主要介紹了 loggerwatch 命令以及如何實如今線代碼熱更新,後續有使用到其餘命令再來補充,詳細信息能夠查閱官方文檔。

相關文章
相關標籤/搜索