arthas助力线上问题分析-基本使用篇

1 Arthas 介绍

1.1 Arthas是什么

Arthas 是Alibaba开源的Java诊断工具。它支持JDK 6+,支持Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,
进一步方便进行问题的定位和诊断。

Arthas 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 Github 的项目里的 Issues 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。

开源地址:https://github.com/alibaba/arthas

官方文档:https://alibaba.github.io/arthas

1.2 Arthas使用场景

当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:

  1. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?

  2. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?

  3. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?

  4. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!

  5. 是否有一个全局视角来查看系统的运行状况?

  6. 有什么办法可以监控到JVM的实时运行状态?

  7. 怎么快速定位应用的热点,生成火焰图?

1.3 Arthas怎么用

Arthas 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。

1.3.1 standalone

  1. wget https://alibaba.github.io/arthas/arthas-boot.jar
  2. java -jar arthas-boot.jar执行后,选择需要检测的应用进程id即可

1.3.2 idea plugin

  1. 以idea为例,在应用市场搜索Alibaba Cloud View并点击安装
  2. 添加需要检测的host
  3. 点击more-dignostic,等待一会后,安装成功,选择需要检测的应用进程id即可

2 Arthas 基本使用篇

在了解了什么是 Arthas,以及 Arthas 的启动方式,下面详细介绍 Arthas 的常用使用方式。在使用命令的过程中如果有问题,每个命令都可以是 -h 查看帮助信息。

首先编写一个有各种情况的测试类运行起来,再使用 Arthas 进行问题定位。下面代码模拟了cpu过高,线程阻塞以及死锁。完整代码可以访问https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas获取。

@Slf4j
public class Problems {

    private static int CPU_THREADS = 10;
    private static ExecutorService executorService = Executors.newFixedThreadPool(CPU_THREADS);

    public static void start() {
        // 模拟 CPU 过高
        cpu();
        // 模拟线程阻塞
        thread();
        // 模拟线程死锁
        deadThread();
    }

    /**
     * 模拟cpu高损耗
     */
    private static void cpu() {
        for (int i = 0; i < CPU_THREADS; i++) {
            executorService.submit(new Thread(() -> {
                while (true) {
                    cpurun();
                }
            }));
        }
    }

    /**
     * 只有完整退出的方法才可以被热更新
     */
    private static void cpurun() {
        log.info("cpu start");
//        try {
//            Thread.sleep(10000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
    }

    /**
     * 模拟线程阻塞,向已经满了的线程池提交线程
     */
    private static void thread() {
        // 添加到线程
        executorService.submit(new Thread(() -> {
            while (true) {
                log.debug("thread start");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }));
    }

    /**
     * 死锁
     */
    private static void deadThread() {
        Object resourceA = new Object();
        Object resourceB = new Object();
        Thread threadA = new Thread(() -> {
            synchronized (resourceA) {
                log.info(Thread.currentThread() + " get ResourceA");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info(Thread.currentThread() + "waiting get resourceB");
                synchronized (resourceB) {
                    log.info(Thread.currentThread() + " get resourceB");
                }
            }
        });

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

2.1 全局监控

首先可以执行dashboard命令,可以监控内存,GC,线程,运行环境等,如下图所示。

arthas助力线上问题分析-基本使用篇_第1张图片
ccc6c0fa.png

2.2 线程状态监控

2.2.1 CPU 状态监控

根据代码,可以看到cpu()方法是一个死循环打印,非常占用cpu。

输入thread 查看线程的CPU占用情况,将通过CPU占比倒序输出线程情况,可以看到前10个线程总计cpu占用100%。

[arthas@3599812]$ thread
Threads Total: 41, NEW: 0, RUNNABLE: 12, BLOCKED: 2, WAITING: 23, TIMED_WAITING: 4, TERMINATED: 0                                                                       
ID            NAME                                      GROUP                       PRIORITY      STATE         %CPU          TIME          INTERRUPTED   DAEMON        
33            pool-1-thread-2                           main                        5             RUNNABLE      10            0:18          false         false         
43            pool-1-thread-7                           main                        5             WAITING       10            0:18          false         false         
45            pool-1-thread-8                           main                        5             WAITING       10            0:18          false         false         
47            pool-1-thread-9                           main                        5             WAITING       10            0:18          false         false        ```

然后可以输入thread 线程id查看线程的堆栈信息,可以迅速定位到高cpu损耗的代码位置。

[arthas@3599812]$ thread 33
"pool-1-thread-2" Id=33 WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@77efb74e
    at sun.misc.Unsafe.park(Native Method)
    -  waiting on java.util.concurrent.locks.ReentrantLock$NonfairSync@77efb74e
    at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
    at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
    at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
    at ch.qos.logback.core.OutputStreamAppender.writeBytes(OutputStreamAppender.java:197)
    at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:231)
    at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
    at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
    at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
    at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
    at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
    at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
    at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
    at ch.qos.logback.classic.Logger.info(Logger.java:579)
    at com.hy.arthas.Problems.cpurun(Problems.java:48)
    at com.hy.arthas.Problems.lambda$cpu$0(Unknown Source)
    at com.hy.arthas.Problems$$Lambda$370/551479935.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

    Number of locked synchronizers = 1
    - java.util.concurrent.ThreadPoolExecutor$Worker@1dde4cb2

Affect(row-cnt:0) cost in 150 ms.

如果线上遇到类似问题,如何在不停机的情况下快速处理呢?先卖一个关子,后面介绍。

2.2.2 线程池查看

通过thread | grep pool 命令查看线程池状态,可以看到线程池中有一个WAITING线程。

[arthas@3589152]$ thread | grep pool
31            pool-1-thread-1                           main                        5             TIMED_WAITING 0             0:0           false         false        
49            pool-1-thread-10                          main                        5             TIMED_WAITING 0             0:0           false         false        
33            pool-1-thread-2                           main                        5             TIMED_WAITING 0             0:0           false         false        
35            pool-1-thread-3                           main                        5             TIMED_WAITING 0             0:0           false         false        
37            pool-1-thread-4                           main                        5             TIMED_WAITING 0             0:0           false         false        
39            pool-1-thread-5                           main                        5             TIMED_WAITING 0             0:0           false         false        
41            pool-1-thread-6                           main                        5             TIMED_WAITING 0             0:0           false         false        
43            pool-1-thread-7                           main                        5             TIMED_WAITING 0             0:0           false         false        
45            pool-1-thread-8                           main                        5             TIMED_WAITING 0             0:0           false         false        
47            pool-1-thread-9                           main                        5             TIMED_WAITING 0             0:0           false         false        
60            pool-2-thread-1                           system                      5             WAITING       0             0:0           false         false  

2.2.3 线程池死锁排查

在之前代码中模拟了一个线程死锁的情况,通过thread --state BLOCKED命令直接定位死锁线程

[arthas@3589152]$ thread --state BLOCKED
Threads Total: 40, NEW: 0, RUNNABLE: 10, BLOCKED: 2, WAITING: 14, TIMED_WAITING: 14, TERMINATED: 0                                                                      
ID            NAME                                      GROUP                       PRIORITY      STATE         %CPU          TIME          INTERRUPTED   DAEMON        
52            Thread-16                                 main                        5             BLOCKED       51            0:0           false         false         
51            Thread-15                                 main                        5             BLOCKED       48            0:0           false         false         
Affect(row-cnt:0) cost in 102 ms.

然后根据线程ID打印堆栈,定位问题代码。

[arthas@3589152]$ thread 52
"Thread-16" Id=52 BLOCKED on java.lang.Object@53687148 owned by "Thread-15" Id=51
    at com.hy.arthas.Problems.lambda$deadThread$3(Problems.java:97)
    -  blocked on java.lang.Object@53687148
    -  locked java.lang.Object@2a8415ca
    at com.hy.arthas.Problems$$Lambda$373/1645547422.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

Affect(row-cnt:0) cost in 74 ms.
[arthas@3589152]$ thread 51
"Thread-15" Id=51 BLOCKED on java.lang.Object@2a8415ca owned by "Thread-16" Id=52
    at com.hy.arthas.Problems.lambda$deadThread$2(Problems.java:82)
    -  blocked on java.lang.Object@2a8415ca
    -  locked java.lang.Object@53687148
    at com.hy.arthas.Problems$$Lambda$372/1820383114.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

Affect(row-cnt:0) cost in 61 ms.

2.3 classloader相关信息

为了方便后续教程的演示,编写一个springboot-web程序,完整代码可以访问https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas获取。

ArthasController.java

@RestController
@Slf4j
public class ArthasController {
    @Autowired
    private ArthasService arthasService;

    @RequestMapping(value = "/get")
    public Map getInfo(Integer uid) throws Exception {
        log.info("get "+uid);
        arthasService.get(uid);
        Map hashMap = new HashMap<>(1);
        hashMap.put("uid", uid);
        return hashMap;
    }

    @RequestMapping(value = "/put")
    public void putTest(String uid) {
        log.info("put "+uid);
        arthasService.putVal(uid);
    }
}

ArthasService.java

@Service
@Slf4j
public class ArthasService {
    private static List cache = new ArrayList<>();
    private List fieldCache = new ArrayList<>();

    public void get(Integer uid) throws Exception {
        check(uid);
        small(uid);
        medium(uid);
        high(uid);
    }

    public void small(Integer uid) throws Exception {
        int count = 0;
        for (int i = 0; i < 10; i++) {
            count += i;
        }
        log.info("small end {}", count);
    }

    public void medium(Integer uid) throws Exception {
        int count = 0;
        for (int i = 0; i < 10000; i++) {
            count += i;
        }
        log.info("medium  end {}", count);
    }

    public void high(Integer uid) throws Exception {
        long count = 0;
        for (int i = 0; i < 10000000; i++) {
            count += i;
        }
        log.info("high end {}", count);
    }

    public boolean check(Integer uid) throws Exception {
        if (uid == null || uid < 0) {
            log.error("uid不正确,uid:{}", uid);
            throw new Exception("uid不正确");
        }
        return true;
    }

    public Map putVal(String uid) {
        cache.add("uid===>" + uid);
        fieldCache.add("local:"+uid);
        return cache.stream().collect(Collectors.toMap(String::toString, String::toUpperCase, (key1, key2) -> key2));
    }
}

2.3.1 查看方法信息

使用sm查看类的方法信息

[arthas@3294199]$ sm com.hy.arthas.service.ArthasService
com.hy.arthas.service.ArthasService ()V
com.hy.arthas.service.ArthasService small(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService medium(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService get(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService check(Ljava/lang/Integer;)Z
com.hy.arthas.service.ArthasService high(Ljava/lang/Integer;)V
com.hy.arthas.service.ArthasService putVal(Ljava/lang/Integer;)V
Affect(row-cnt:7) cost in 17 ms.

2.3.2 查看类与变量信息

使用sc -d -f命令获取类的详细信息以及变量信息

[arthas@3625353]$ sc -d -f com.hy.arthas.service.ArthasService
 class-info        com.hy.arthas.service.ArthasService                                                                                                                  
 code-source       file:/home/admin/zhy/arthastest/hongyiarthas-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/                                                                    
 name              com.hy.arthas.service.ArthasService                                                                                                                  
 isInterface       false                                                                                                                                                
 isAnnotation      false                                                                                                                                                
 isEnum            false                                                                                                                                                
 isAnonymousClass  false                                                                                                                                                
 isArray           false                                                                                                                                                
 isLocalClass      false                                                                                                                                                
 isMemberClass     false                                                                                                                                                
 isPrimitive       false                                                                                                                                                
 isSynthetic       false                                                                                                                                                
 simple-name       ArthasService                                                                                                                                        
 modifier          public                                                                                                                                               
 annotation        org.springframework.stereotype.Service                                                                                                               
 interfaces                                                                                                                                                             
 super-class       +-java.lang.Object                                                                                                                                   
 class-loader      +-org.springframework.boot.loader.LaunchedURLClassLoader@238e0d81                                                                                    
                     +-sun.misc.Launcher$AppClassLoader@55f96302                                                                                                        
                       +-sun.misc.Launcher$ExtClassLoader@49594137                                                                                                      
 classLoaderHash   238e0d81                                                                                                                                             
 fields            name     log                                                                                                                                         
                   type     org.slf4j.Logger                                                                                                                            
                   modifier final,private,static                                                                                                                        
                   value    Logger[com.hy.arthas.service.ArthasService]                                                                                                 
                                                                                                                                                                        
                   name     cache                                                                                                                                       
                   type     java.util.List                                                                                                                              
                   modifier private,static                                                                                                                              
                   value    [uid===>2, uid===>3, uid===>4]                                                                                                              
                                                                                                                                                                        
                   name     fieldCache                                                                                                                                  
                   type     java.util.List                                                                                                                              
                   modifier private 

2.3.3 变量访问与控制

使用ognl表达式可以很方便控制变量的行为,多用作静态变量。

更多的例子可以参考ognl官网以及arthas官网

  1. 首先使用curl对应用发送请求get请求
curl --location --request GET 'http://33.69.2.53:3890/put' --form 'uid=3'
  1. 执行ognl命令获取类静态变量cache的值。
ognl '@com.hy.arthas.service.ArthasService@cache'

如果发生找不到类的错,需要指定classloader,如下所示,可以看到cache值被打印了,重复执行可以看到cache的值增加。

[arthas@3294199]$ sc -d com.hy.arthas.service.ArthasService | grep Hash
 classLoaderHash   238e0d81
 
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache'
@ArrayList[
    @String[uid===>3],
]

curl --location --request GET 'http://cdh219:3890/put' --form 'uid=1'

[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache'
@ArrayList[
    @String[uid===>3],
    @String[uid===>1],
]
  1. 使用ognl命令动态变更静态变量

ognl表达式有非常强大的功能,就是可以调用变量的函数。如下所示,可以调用add,clear,size等函数。

[arthas@3294199]$ ognl -c 238e0d81 '@[email protected]("test_add")'
@Boolean[true]
[arthas@3294199]$ ognl -c 238e0d81 '@com.hy.arthas.service.ArthasService@cache'
@ArrayList[
    @String[uid===>3],
    @String[uid===>1],
    @String[test_add],
]


[arthas@3294199]$ ognl -c 238e0d81 '@[email protected]()'
null
[arthas@3294199]$ ognl -c 238e0d81 '@[email protected]()'
@Integer[0]

这功能除了解决问题外,感觉还可以应用到很多其他地方,比如根据业务需求临时变更一些变量,以达到管控作用。

  1. 获取系统环境

ognl表达式的其他用法。

[arthas@3294199]$ ognl '#value1=@System@getProperty("java.home"), #value2=@System@getProperty("java.runtime.name"), {#value1, #value2}'
@ArrayList[
    @String[/usr/java/jdk1.8.0_171/jre],
    @String[Java(TM) SE Runtime Environment],
]

2.3.4 观察方法运行耗时

当程序没问题时,有时候也需要密切观测程序的运行状态,比如方法的rt时间,针对长时间响应的方法做优化。arthas支持每次调用的耗时统计以及统计耗时。

2.3.4.1 方法运行耗时

首先介绍一下方法运行耗时监控。运行trace命令,trace 类名 方法名监控函数,然后发送请求

curl --location --request GET 'http://cdh219:3890/get' --form 'uid=4'

可以看到com.hy.arthas.service.ArthasService:get()的方法耗时最长。

[arthas@3294199]$ trace com.hy.arthas.controller.ArthasController getInfo
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 44 ms.
`---ts=2021-01-18 21:29:18;thread_name=http-nio-3890-exec-5;id=15;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@7c417213
    `---[10.511479ms] com.hy.arthas.controller.ArthasController:getInfo()
        +---[0.693079ms] org.slf4j.Logger:info() #29
        `---[9.332481ms] com.hy.arthas.service.ArthasService:get() #30

然后继续定位内部的方法com.hy.arthas.service.ArthasService:get(),然后再次发送请求

curl --location --request GET 'http://cdh219:3890/get' --form 'uid=4'

可以准确定位到high()耗时最长。

[arthas@3294199]$ trace com.hy.arthas.service.ArthasService get
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 40 ms.
`---ts=2021-01-18 21:30:38;thread_name=http-nio-3890-exec-6;id=16;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@7c417213
    `---[22.246322ms] com.hy.arthas.service.ArthasService:get()
        +---[0.039841ms] com.hy.arthas.service.ArthasService:check() #23
        +---[0.368773ms] com.hy.arthas.service.ArthasService:small() #24
        +---[0.670825ms] com.hy.arthas.service.ArthasService:medium() #25
        `---[18.308464ms] com.hy.arthas.service.ArthasService:high() #26

2.3.4.2 统计方法耗时

对于一些重点方法,需要测试一下调用的压力,以便于找到瓶颈点重点优化。

采用monitor,可以间隔c秒重复n次的监控,继续发送n次之前的请求,可以看到每隔5秒的调用次数,失败成功数以及平均rt时间。

[arthas@3294199]$ monitor -c 5 -n 3 com.hy.arthas.service.ArthasService get
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 35 ms.
 timestamp            class                                method  total  success  fail  avg-rt(ms)  fail-rate                                                          
---------------------------------------------------------------------------------------------------------------                                                         
 2021-01-18 21:37:16  com.hy.arthas.service.ArthasService  get     3      3        0     12.83       0.00%                                                              

 timestamp            class                                method  total  success  fail  avg-rt(ms)  fail-rate                                                          
---------------------------------------------------------------------------------------------------------------                                                         
 2021-01-18 21:37:21  com.hy.arthas.service.ArthasService  get     3      3        0     9.17        0.00%                                                              

 timestamp            class                                method  total  success  fail  avg-rt(ms)  fail-rate                                                          
---------------------------------------------------------------------------------------------------------------                                                         
 2021-01-18 21:37:26  com.hy.arthas.service.ArthasService  get     2      2        0     5.76        0.00%                                                              

Command execution times exceed limit: 3, so command will exit. You can set it with -n option.

2.3.5 方法执行数据观测

2.3.5.1 方法出入参观测

线上有时候出bug需要快速知晓某个方法的入参和出参,也是可以做到的。

使用 watch 命令轻松查看输入输出参数以及异常等信息。执行后再发送之前的curl命令测试。

#返回入参以及出参
[arthas@3355851]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 43 ms.
ts=2021-01-18 22:27:20; [cost=3.134463ms] result=@ArrayList[
    @String[6],
    @HashMap[isEmpty=false;size=3],
]

#返回入参以及出参size(Map)
[arthas@3355851]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj.size()}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 43 ms.
ts=2021-01-18 22:28:08; [cost=2.868114ms] result=@ArrayList[
    @String[7],
    @Integer[4],
]

#打印入参和返回值toString
[arthas@3355851]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj.toString()}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 42 ms.
ts=2021-01-18 22:28:43; [cost=3.754227ms] result=@ArrayList[
    @String[10],
    @String[{uid===>10=UID===>10, uid===>6=UID===>6, uid===>7=UID===>7, uid===>1=UID===>1, uid===>4=UID===>4}],
]

#打印入参,返回值toString,以及类中成员变量
[arthas@3363085]$ watch com.hy.arthas.service.ArthasService putVal '{params[0],returnObj.toString(),target.fieldCache.toString()}'
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 35 ms.
ts=2021-01-18 22:39:26; [cost=2.939916ms] result=@ArrayList[
    @String[2],
    @String[{uid===>1=UID===>1, uid===>2=UID===>2}],
    @String[[local:1, local:2]],
]

2.3.5.2 方法调用堆栈

使用stack可以查看被执行的方法调用堆栈

[arthas@3363085]$ stack com.hy.arthas.service.ArthasService putVal
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 55 ms.
ts=2021-01-18 22:43:22;thread_name=http-nio-3890-exec-5;id=15;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@a4102b8
    @com.hy.arthas.service.ArthasService.putVal()
        at com.hy.arthas.controller.ArthasController.putTest(ArthasController.java:39)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-2)
        ...

2.3.5.3 TimeTunnel

watch 虽然很方便和灵活,但需要提前想清楚观察表达式的拼写,这对排查问题而言要求太高,因为很多时候我们并不清楚问题出自于何方,只能靠蛛丝马迹进行猜测。

这个时候如果能记录下当时方法调用的所有入参和返回值、抛出的异常会对整个问题的思考与判断非常有帮助。

于是乎,TimeTunnel 命令就诞生了。

监控函数,发起请求后记录每次调用。

[arthas@3625353]$ tt -t com.hy.arthas.service.ArthasService get
Press Q or Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 51 ms.
 INDEX     TIMESTAMP                 COST(ms)     IS-RET     IS-EXP    OBJECT              CLASS                                  METHOD                                
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 1003      2021-01-19 18:36:18       17.034313    true       false     0x1e4d527b          ArthasService                          get                                   
 1004      2021-01-19 18:36:26       13.678166    true       false     0x1e4d527b          ArthasService                          get                                   
 1005      2021-01-19 18:36:30       8.297877     true       false     0x1e4d527b          ArthasService                          get                                   
 1006      2021-01-19 18:36:33       8.80053      true       false     0x1e4d527b          ArthasService                          get  

可以任意访问之前的版本,可以看到每次调用的信息,比如参数返回值是否异常等。

[arthas@3625353]$ tt -i 1003
 INDEX          1003                                                                                                                                                    
 GMT-CREATE     2021-01-19 18:36:18                                                                                                                                     
 COST(ms)       17.034313                                                                                                                                               
 OBJECT         0x1e4d527b                                                                                                                                              
 CLASS          com.hy.arthas.service.ArthasService                                                                                                                     
 METHOD         get                                                                                                                                                     
 IS-RETURN      true                                                                                                                                                    
 IS-EXCEPTION   false                                                                                                                                                   
 PARAMETERS[0]  @Integer[4]                                                                                                                                             
 RETURN-OBJ     null       

还可以重新发起一次之前的请求,而不用页面请求,更方便当时现场问题的重现。

[arthas@3625353]$ tt -i 1004 -p
 RE-INDEX       1004                                                                                                                                                    
 GMT-REPLAY     2021-01-19 18:38:36                                                                                                                                     
 OBJECT         0x1e4d527b                                                                                                                                              
 CLASS          com.hy.arthas.service.ArthasService                                                                                                                     
 METHOD         get                                                                                                                                                     
 PARAMETERS[0]  @Integer[1]                                                                                                                                             
 IS-RETURN      true                                                                                                                                                    
 IS-EXCEPTION   false                                                                                                                                                   
 COST(ms)       10.391835                                                                                                                                               
 RETURN-OBJ     null                                                                                                                                                    
Time fragment[1004] successfully replayed 1 times.

3 实例代码

以上所有实例代码可以在https://github.com/pj1987111/hongyinotes/tree/main/hongyiarthas中找到。

4 接下来

如果看完了这些命令使用的话,基本上可以完成所有的关于线上系统排错、诊断的任务了。
接下来请期待关于Arthas的第二篇文章《arthas助力线上问题分析-线上实战篇》希望您能从中获得帮助。

你可能感兴趣的:(arthas助力线上问题分析-基本使用篇)