アリババのオープンソースJava診断ツールArthas - 実戦

目次

参考

Alibaba のオープン ソース Java 診断ツール Arthas-Advanced チュートリアル
Java 診断ツール Arthas に夢中になる
arthas-idea-plugin
ユーザー プラクティス

1.開始

# 避免中文乱码
wget https://arthas.aliyun.com/arthas-boot.jar;java -jar arthas-boot.jar --target-ip 0.0.0.0
java -Dfile.encoding=UTF-8 -jar arthas-boot.jar

ここに画像の説明を挿入

2. ognl式のサポート

  • ローダ
  • クラッツ
  • 方法
  • 目標
  • パラメータ
  • returnObj
  • throwExp
  • は前に
  • isThrow
  • isReturn

3. 監視パラメータ

# 监听所有参数
watch com.xxx.iot.web.DeviceController * '{params}' -x 2
# 监听所有参数
watch com.xxx.iot.web.DeviceController * params -x 2
# 监听第几个参数
watch com.xxx.iot.web.DeviceController * params[0] -x 2

-x特定のパラメータと結果の内容を出力するために調整できるトラバーサルの深さを示します。デフォルト値は 1 です。

コントローラーのパラメーターと戻り値を監視する

完全なパラメーターと戻り値をリッスンする

# 只监听参数
watch com.xxx.iot.web.DeviceController * '{params}' -x 2
# 或者
watch com.xxx.iot.web.DeviceController * params -x 2
# 监听所有方法 
watch com.xxx.iot.web.DeviceController * '{params, target, returnObj}' -x 2
# 监听对应方法
watch com.xxx.iot.web.DeviceController getOnlineByCode '{params, target, returnObj}' -x 2

依頼内容が以下の場合

Result<PageDTO<DriverDTO>> fetchDriverByDeviceCode(@Validated @Parameter(description = "实体", name = "param", required = true) @RequestBody DevicePageQueryDTO param)

ここに画像の説明を挿入

arthas は次のように表示されます。

メソッド = com.xxx.iot.web.DeviceController.getOnlineByCode 場所 = AtExit
ts = 2023-04-10 16:57:49; [コスト=16.8166ms] 結果=@ArrayList[
@Object[][
@DeviceCodeBatchQueryDTO[DeviceCodeBatchQueryDTO(deviceCodes=[1584381959985602561, 1589581864182480897, 150077101, TJ0205103, TJ0205211])],
log =@
DeviceController[
ger@Logger[] .xxx.iot.web.DeviceController]]、
deviceService=@DeviceServiceImpl[com.xxx.iot.service.impl.DeviceServiceImpl@5236007a]、
]、
@Result[
serialVersionUID=@Long[1]、
traceId=null、
code= @Integer[0]、
msg=null、
data=@ArrayList[isEmpty=false;size=5]、
uri=null、
]、
]

カフカの消費を監視する

# 方法 public void process(JSONObject messageBody,PhysicalDataModelDTO tdl,DeviceType deviceType,ProductDTO product,String productKey,String deviceKey, String messageId,String deviceMessageId) {)
watch com.xxx.iot.receiver.listener.KafkaPropertyReceiver process '{params, target, returnObj}' -x 2

単一のパラメーターをリッスンする

watch com.xxx.iot.receiver.listener.KafkaPropertyReceiver process '{params[0], target, returnObj}' -x 2

効果は次のとおりです。

method=com.xxx.xxx.receiver.listener.KafkaPropertyReceiver.process location=AtExit
ts=2023-04-10 17:14:30; [cost=24.4472ms] result=@ArrayList[
    @JSONObject[
        @String[deviceKey]:@String[yangchen1],
        @String[messageId]:@String[54691],
        @String[params]:@JSONObject[isEmpty=false;size=11],
        @String[productKey]:@String[cu9f6bf82fc4444cc18774f2bc7d370685],
        @String[ts]:@Long[1681118069925],
        @String[version]:@String[1.0],
    ],
    @KafkaPropertyReceiver[
        log=@Logger[Logger[com.xxx.iot.receiver.listener.KafkaPropertyReceiver]],
        pushService=@PushServiceImpl[com.xxx.iot.push.impl.PushServiceImpl@1703b898],
        mongoTemplate=@MongoTemplate[org.springframework.data.mongodb.core.MongoTemplate@13857408],
        PARAMS=@String[params],
        productService=@ProductServiceImpl[com.xxx.iot.service.impl.ProductServiceImpl@196a3471],
        log=@Logger[Logger[com.xxx.iot.receiver.listener.KafkaPropertyReceiver]],
        title=@String[属性上报],
    ],
    null,
]

例外をリッスンする

watch コマンドは -e オプションをサポートしています。これは、例外がスローされたときにのみ要求をキャプチャすることを意味します。

watch com.example.demo.arthas.user.UserController * "{params[0],throwExp}" -e

時間でフィルター

watch コマンドは、リクエスト時間によるフィルタリングをサポートしています。次に例を示します。

watch com.example.demo.arthas.user.UserController * '{params, returnObj}' '#cost>200'

パラメータ比較の監視

user/1 にアクセスするとき、watch コマンドには出力がありません

user/101 にアクセスすると、watch は結果を出力します。

watch com.example.demo.arthas.user.UserController * returnObj 'params[0] > 100'

4. 変数とメソッド

静的メンバー変数値のクエリ

ognl @com.xxx.iot.ArthasTest@hashSet

構成クラスの特定の属性の値を照会します

## 方法一
ognl '@com.xxx.common.core.util.ServiceHelper@getBean("mqConfig").eventConcurrency'

クラスローダーを介してSpringコンテナー内のオブジェクトのすべてのプロパティを表示します

# 1、获取类加载器
[arthas@17860]$ sc -d *MqConfig | grep class-loader
class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2
# 2、获取对象属性
[arthas@17860]$ vmtool --action getInstances -c 18b4aac2 --className com.xxx.iot.mq.config.MqConfig  --limit 10 -x 2
@MqConfig[][
    @MqConfig[
        propertyConcurrency=@Integer[3],
        eventConcurrency=@Integer[1],
    ],
    @MqConfig$$EnhancerBySpringCGLIB$$1[
  • -c [クラスローダーのハッシュ]
  • -x レベルを設定
# 设置x = 2 无法查看具体
[arthas@7]$ vmtool --action getInstances -c 31221be2 --className com.xxx.iot.config.IotConfig --limit 10 -x 2
@IotConfig[][
    @IotConfig[
        timeout=@Integer[10],
        whiteList=@LinkedHashSet[isEmpty=false;size=3],
        aiProductKey=@String[cu623ef0a8e8564602b464b8021d30f9ab],
        aiHost=@String[172.17.0.1,1xx.x0.2x4.xx7],
        apps=@ArrayList[isEmpty=false;size=4],
    ],

# 设置x = 3 可以查看第二层数据
[arthas@7]$ vmtool --action getInstances -c 31221be2 --className com.xxx.iot.config.IotConfig --limit 10 -x 3
@IotConfig[][
    @IotConfig[
        timeout=@Integer[10],
        whiteList=@LinkedHashSet[
            @String[1xx.x0.2x4.xx7],
            @String[127.0.0.1],
        ],
        aiProductKey=@String[cu623ef0a8e8564602b464b8021d30f9ab],
        aiHost=@String[172.17.0.1,1xx.x0.2x4.xx7],
        apps=@ArrayList[
            @AppDTO[AppDTO(title=null, appKey=12201, appSecret=12301, whiteList=null)],
            @AppDTO[AppDTO(title=null, appKey=33, appSecret=23342, whiteList=null)],
            @AppDTO[AppDTO(title=本地测试, appKey=158173395456, appSecret=O0x7M7TE6AjDsUqxIfZ8zg0Y, whiteList=[127.0.0.1, 192.168.0.44, 192.168.0.88 ])],
            @AppDTO[AppDTO(title=xx信息, appKey=637173395456, appSecret=O0x7M7T6AjwrerwsdX8xgXa, whiteList=[127.0.0.1, 192.168.0.44, 192.168.0.88])],
        ],
    ]

静的メソッドを実行

# 1、获取类加载的hash
sc -d com.xxx.iot.util.IotCacheUtil
# 2、执行带参数
ognl -c 18b4aac2 '@com.xxx.iot.util.IotCacheUtil@getTdl("cu623ef0a8e8564602b464b8021d30f9ab")' -x 4

五、逆コンパイル

jad com.example.demo.arthas.user.UserController
# 反编译到制定文件
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java

6. logLevel ログ レベルを変更する

クラスのクラスローダーを表示する

# 下面是模糊查找,也可以精确查找sc -d com.xxx.iot.web.DeviceController | grep class-loader
[arthas@17860]$ sc -d *DeviceController | grep class-loader
 class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2
 class-loader      +-sun.misc.Launcher$AppClassLoader@18b4aac2

ognl でロガーを取得する

[arthas@17860]$ ognl --classLoaderClass sun.misc.Launcher$AppClassLoader '@com.xxx.iot.web.DeviceController@log'
@Logger[
    serialVersionUID=@Long[5454405123156820674],
    FQCN=@String[ch.qos.logback.classic.Logger],
    name=@String[com.xxx.iot.web.DeviceController],
    level=null,
    effectiveLevelInt=@Integer[10000],
    parent=@Logger[Logger[com.xxx.iot.web]],
    childrenList=null,
    aai=null,
    additive=@Boolean[true],
    loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[logback]],
    lastUpdateCheckTime=@Long[1681119603828],
]

DeviceController@logger が実際に logback を使用していることがわかります。level=null であることがわかります。これは、実際の最終レベルがルート ロガーから取得されることを意味します。

レベルを設定

DeviceControllerのロガーレベルを別途設定

ognl --classLoaderClass sun.misc.Launcher$AppClassLoader '@[email protected](@ch.qos.logback.classic.Level@DEBUG)'

DeviceController@logger を再度取得すると、既に DEBUG になっていることがわかります。

[arthas@17860]$ ognl --classLoaderClass sun.misc.Launcher$AppClassLoader '@com.xxx.iot.web.DeviceController@lo'
@Logger[
    serialVersionUID=@Long[5454405123156820674],
    FQCN=@String[ch.qos.logback.classic.Logger],
    name=@String[com.xxx.iot.web.DeviceController],
    level=@Level[DEBUG],
    effectiveLevelInt=@Integer[10000],
    parent=@Logger[Logger[com.xxx.iot.web]],
    childrenList=null,
    aai=null,
    additive=@Boolean[true],
    loggerContext=@LoggerContext[ch.qos.logback.classic.LoggerContext[logback]],
    lastUpdateCheckTime=@Long[1681119814259],
]

logback のグローバル ロガー レベルを変更する (非推奨)

ルート ロガーを取得すると、グローバル ロガー レベルを変更できます。

ognl --classLoaderClass sun.misc.Launcher$AppClassLoader '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'

logger を使用して logback のログ レベルを変更し、yml のログのログ レベルに対応するようにします。

logging:
  level:
    org.springframework.data.mongodb.core.MongoTemplate: DEBUG

情報に変更

# 查看日志级别
logger --name org.springframework.data.mongodb.core.MongoTemplate
# 修改
logger --name org.springframework.data.mongodb.core.MongoTemplate --level info

7.終了

# 退出当前监听
exit
# 退出整个程序
stop

八、CPU高すぎ、スレッドデッドロック実戦

参考

Arthas を使用して、Java アプリケーションの CPU 負荷が高い問題を正確に突き止めます #1202

テストコード

public class ArthasTest {
    
    
    private static HashSet hashSet = new HashSet();

    public static void main(String[] args) {
    
    
        // 模拟 CPU 过高
        cpuHigh();
        // 模拟线程死锁
        deadThread();
        // 不断的向 hashSet 集合增加数据
        addHashSetThread();
    }

    /**
     * 不断的向 hashSet 集合添加数据
     */
    public static void addHashSetThread() {
    
    
// 初始化常量
        new Thread(() -> {
    
    
            int count = 0;
            while (true) {
    
    
                try {
    
    
                    hashSet.add("count" + count);
                    Thread.sleep(1000);
                    count++;
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static void cpuHigh() {
    
    
        new Thread(() -> {
    
    
            while (true) {
    
    

            }
        }).start();
    }

    /**
     * 死锁
     */
    private static void deadThread() {
    
    
        /** 创建资源 */
        Object resourceA = new Object();
        Object resourceB = new Object();
        // 创建线程
        Thread threadA = new Thread(() -> {
    
    
            synchronized (resourceA) {
    
    
                System.out.println(Thread.currentThread() + " get ResourceA");
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourceB");
                synchronized (resourceB) {
    
    
                    System.out.println(Thread.currentThread() + " get resourceB");
                }
            }
        });

        Thread threadB = new Thread(() -> {
    
    
            synchronized (resourceB) {
    
    
                System.out.println(Thread.currentThread() + " get ResourceB");
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resourceA");
                synchronized (resourceA) {
    
    
                    System.out.println(Thread.currentThread() + " get resourceA");
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

スレッドを表示

thread
Threads Total: 49, NEW: 0, RUNNABLE: 9, BLOCKED: 2, WAITING: 4, TIMED_WAITING: 4, TERMINATED: 0, Internal threads: 30
ID   NAME                          GROUP          PRIORITY  STATE    %CPU      DELTA_TIM TIME      INTERRUPT DAEMON
22   Thread-0                      main           5         RUNNABLE 65.65     0.140     0:41.265  false     false
2    Reference Handler             system         10        WAITING  0.0       0.000     0:0.000   false     true
3    Finalizer                     system         8         WAITING  0.0       0.000     0:0.000   false     true
4    Signal Dispatcher             system         9         RUNNABLE 0.0       0.000     0:0.000   false     true
5    Attach Listener               system         5         RUNNABLE 0.0       0.000     0:0.015   false     true
28   arthas-timer                  system         5         WAITING  0.0       0.000     0:0.000   false     true
30   Keep-Alive-Timer              system         8         TIMED_WA 0.0       0.000     0:0.000   false     true
31   arthas-NettyHttpTelnetBootstr system         5         RUNNABLE 0.0       0.000     0:0.000   false     true
32   arthas-NettyWebsocketTtyBoots system         5         RUNNABLE 0.0       0.000     0:0.000   false     true
33   arthas-NettyWebsocketTtyBoots system         5         RUNNABLE 0.0       0.000     0:0.000   false     true
34   arthas-shell-server           system         5         TIMED_WA 0.0       0.000     0:0.000   false     true
35   arthas-session-manager        system         5         TIMED_WA 0.0       0.000     0:0.000   false     true
36   arthas-UserStat               system         5         WAITING  0.0       0.000     0:0.000   false     true
38   arthas-NettyHttpTelnetBootstr system         5         RUNNABLE 0.0       0.000     0:0.000   false     true
39   arthas-command-execute        system         5         RUNNABLE 0.0       0.000     0:0.000   false     true
23   Thread-1                      main           5         BLOCKED  0.0       0.000     0:0.000   false     false
24   Thread-2                      main           5         BLOCKED  0.0       0.000     0:0.000   false     false
25   Thread-3                      main           5         TIMED_WA 0.0       0.000     0:0.000   false     false
26   DestroyJavaVM                 main           5         RUNNABLE 0.0       0.000     0:0.046   false     false
-1   Service Thread                -              -1        -        0.0       0.000     0:0.000   false     true
-1   C1 CompilerThread9            -              -1        -        0.0       0.000     0:0.031   false     true
-1   C1 CompilerThread8            -              -1        -        0.0       0.000     0:0.000   false     true
-1   C1 CompilerThread10           -              -1        -        0.0       0.000     0:0.015   false     true
-1   C1 CompilerThread11           -              -1        -        0.0       0.000     0:0.000   false     true
-1   GC task thread#11 (ParallelGC -              -1        -        0.0       0.000     0:0.000   false     true
-1   GC task thread#10 (ParallelGC -              -1        -        0.0       0.000     0:0.000   false     true
-1   C2 CompilerThread2

CPU が高すぎるスレッド スタックを表示する

[arthas@1948]$ thread 22
"Thread-0" Id=22 RUNNABLE
    at com.xxx.iot.ArthasTest.lambda$cpuHigh$1(ArthasTest.java:42)
    at com.xxx.iot.ArthasTest$$Lambda$1/1711574013.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

スレッド プールのデッドロックのトラブルシューティング

thread --state BLOCKED

スレッドのデッドロックを表示

[arthas@1948]$ thread -b
"Thread-1" Id=23 BLOCKED on java.lang.Object@4db472ec owned by "Thread-2" Id=24
    at com.xxx.iot.ArthasTest.lambda$deadThread$2(ArthasTest.java:66)
    -  blocked on java.lang.Object@4db472ec
    -  locked java.lang.Object@5e0c9ecd <---- but blocks 1 other threads!
    at com.xxx.iot.ArthasTest$$Lambda$2/1674896058.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

逆コンパイル

[arthas@1948]$ jad com.xxx.iot.ArthasTest

変数の値を表示する

ognl @com.xxx.iot.ArthasTest@hashSet

9、トラッキング HTTP リクエストのヒント

1. インターフェースの応答時間を取得する

ちょうど今、私の友人は、arthas 分析によってすべての要求インターフェイスの時間のかかる分析を行う必要があり、それをみんなと共有する必要があります.
方向印刷からファイルへ、そして応答形式に従って統計分析を行うことができます.

watch org.springframework.web.servlet.DispatcherServlet doService '{params[0].getRequestURI()+" "+params[0].getRemoteAddr()+" "+ #cost}'  -n 5  -x 3 '#cost>100'  -f

2. 指定ヘッダの情報を取得する

たとえば、ここで trace-id を取得します

 watch org.springframework.web.servlet.DispatcherServlet doService '{params[0].getRequestURI()+"  header="+params[0].getHeaders("User-Agent")}'  -n 10  -x 3 -f
 
 watch org.springframework.web.servlet.DispatcherServlet doService '{params[0].getRequestURI()+ " " +params[0].getRemoteAddr() +"  header="+params[0].getHeader("x-forwarded-for")}'  -n 10  -x 3 -f
 
 watch com.xxx.iot.interceptor.IotApiInterceptor preHandle '{params[0].getRequestURI()+ " " +params[0].getRemoteAddr() +"  header="+params[0].getHeaders("x-forwarded-for")}'  -n 10  -x 3 -f

10、インターフェースに時間がかかる

インターフェースの時間のかかる分析
通常、arthas を使用してインターフェースの特定の時間のかかる分析を行います。また、スカイワーキングやその他の分散追跡フレームワークを組み合わせて、時間のかかる状況を表示することもできます。
最初のステップは、複数のクラス E を一緒に使用する必要がある特定のインターフェイスの時間がかかることを確認することです。

trace com.wangji92.arthas.plugin.demo.service.impl.ArthasTestServiceImpl doTraceE  -n 5 --skipJDKMethod true 

最初のステップは、時間のかかる分析にのみ関連している可能性があります (プロジェクト最適化バッチは、時間のかかるポジションをキャプチャして分析し、「ブラインド」マッチングも使用できます)

trace com.xxxCompany* * '#cost > 2000'

11、arthas は単純に sql ステートメントを表示します

次の2つのアイデアは、基本的なSQL構造が基本的に利用可能であることを確認します~また、条件式を追加してパラメータをフィルタリングすることもできます

方法 1: ウォッチ接続

基本的なニーズを満たすことができるSQLを表示するための接続を直接監視し、実行パラメータに関する情報はありません。

watch java.sql.Connection prepareStatement '{params,throwExp}'  -n 5  -x 3 

方法 2: BoundSql を監視する

mybatisを使用すると使用できます

watch org.apache.ibatis.mapping.BoundSql getSql '{params,returnObj,throwExp}'  -n 5  -x 3 

おすすめ

転載: blog.csdn.net/Blueeyedboy521/article/details/130065091