线上排查神器arthas总结

简介

Arthas中文名叫做阿尔萨斯

官方地址:https://alibaba.github.io/arthas/

安装

使用arthas-boot (推荐)

下载arthas-boot.jar,然后用java -jar的方式启动:
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

打印帮助信息
java -jar arthas-boot.jar -h

如果下载速度比较慢,可以使用aliyun的镜像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http

如果从github下载有问题,可以使用gitee镜像
curl -O https://arthas.gitee.io/arthas-boot.jar

通过as.sh命令启动

Mac安装as.sh,并设置快捷方式
curl -sk https://arthas.gitee.io/arthas-boot.jar -o ~/.arthas-boot.jar  && echo "alias as.sh='java -jar ~/.arthas-boot.jar --repo-mirror aliyun --use-http'" >> ~/.bashrc && source ~/.bashrc

如果安装了zsh执行下面命令
curl -sk https://arthas.gitee.io/arthas-boot.jar -o ~/.arthas-boot.jar  && echo "alias as.sh='java -jar ~/.arthas-boot.jar --repo-mirror aliyun --use-http'" >> ~/.zshrc && source ~/.zshrc

接下来只需要命令行输入as.sh就可以启动

通过Cloud Toolkit插件直接进入arthas

卸载

  • 在 Linux/Unix/Mac 平台

    删除下面文件:

    rm -rf ~/.arthas/
    rm -rf ~/logs/arthas
    
  • Windows平台直接删除user home下面的.arthaslogs/arthas目录

进入arthas

  1. 先启动需要被诊断的程序,再启动arthas
    java -jar arthas-boot.jar

  2. 选择相应的进程id

  3. 看到如下页面表示arthas,attach成功

  4. 接下来就可以使用arthad相关命令进行诊断程序

退出arthas

如果只是退出当前的连接,可以用quit或者exit命令。Attach到目标进程上的arthas还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想完全退出arthas,可以执行stop命令。

快速入门

常用命令

img

dashboard:仪表板

作用:显示当前系统的实时数据面板,按q或ctrl+c退出

img

jad:反编译某个类,或者反编译某个类的某个方法

作用:把字节码文件反编译成源码

反编译类:

jad com.shinemo.todo.domain.TodoDO 

反编译method:

显示编译后的详情
jad com.shinemo.wangge.core.service.todo.impl.TodoServiceImpl operateTodoThing

只显示源码
jad --source-only com.shinemo.wangge.core.service.todo.impl.TodoServiceImpl operateTodoThing

thread 线程相关命令

作用:查看当前JVM的线程堆栈信息

thread -n:排列出 CPU 使用率 Top N 的线程。

thread id: 显示指定线程的运行堆栈

thread -b: 找出当前阻塞其他线程的线程

死锁案例:

    @GetMapping("/test")
    @SmIgnore
    public ApiResult<String> test() {

        /** 创建资源 */
        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();

        return ApiResult.success("success");
    }

image-20200708172933493

sc:查看JVM已加载的类信息

sc默认开启了子类匹配功能,所以可以用来查看哪些类实现了这个接口.

sc -d:显示详细信息

sc -f:显示所有成员变量,需要配合-d一起使用

sc com.shinemo.wangge.core.handler.UrlRedirectHandler

sm:查看已加载类的方法信息

sm -d:显示详细信息

sm com.shinemo.wangge.web.controller.todo.TodoController *
sm com.shinemo.wangge.web.controller.todo.TodoController getTypeList

watch 方法执行的数据观测

当我们遇到线上数据 bug时,我们一般处理的手段就是开发环境模拟线上数据,从生产日志中查找线索,再或者远程 debug。以上不管哪种排查手段,相对都是比较麻烦。这时Arthas的 watch可以帮助我们查看实时的代码执行情况。使用观察表达式可以查看函数的 参数, 返回值, 异常信息。观察表达式主要由 OGNL表达式组成,所以可以编写 OGNL表达式来执行。

语法:

watch 包名.类名 方法名 想要查看的信息
例子:
watch com.shinemo.wangge.web.controller.todo.TodoController getTodoList '{params,returnObj,throwExp}' -n 5 -x 5 '1==1'

-x表示遍历深度,可以调整来打印具体的参数和结果内容,默认值是1。
-n表示执行次数

查看函数返回值

watch com.shinemo.wangge.web.controller.common.IndexController getIndex returnObj

查看函数的请求参数

watch com.shinemo.wangge.web.controller.common.IndexController getIndex params

异步保存日志

有时我们排查某个函数,不能马上获取到函数的信息, arthas给提供的 后台异步任务可以帮助我们记录日志。使用方式和Linux的类似。

watch com.shinemo.wangge.web.controller.todo.TodoController getTodoList '{params,returnObj,throwExp}' -n 5 -x 5 '1==1' > /data/logs/test.log &

trace:输出方法调用路径,并输出耗时

我们常会遇到调用某个api时rt过长,我们就要找出调用链上的某个或几个函数进行优化,我们通常定位几个可能的锚点,打印各个锚点间的rt。或者从日志中找出日志打印的时间点计算出时间差,不管使用哪种方法都比较繁琐。当使用 arthastrace命令可以轻松的完成我们的需求。

这个指令对于优化代码非常的有用,可以看出具体每个方法执行的时间,如果是for循环等重复语句,还能看出n次循环中的最大耗时,最小耗时,和平均耗时.

 trace com.shinemo.wangge.web.controller.common.IndexController getIndex -n 5 '1==1'

结果如下:

[arthas@13090]$ trace com.shinemo.wangge.web.controller.common.IndexController getIndex -n 5 '1==1'
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 233 ms, listenerId: 3
`---ts=2020-07-08 14:13:09;thread_name=http-nio-20014-exec-4;id=64;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@11e1bd43
    `---[652.290636ms] com.shinemo.wangge.web.controller.common.IndexController:getIndex()
        +---[0.030585ms] com.shinemo.smartgrid.domain.SmartGridContext:getLongUid() #80
        +---[0.012105ms] com.shinemo.smartgrid.domain.SmartGridContext:getMobile() #81
        +---[88.923084ms] com.shinemo.wangge.core.service.stallup.StallUpService:getSimpleInfo() #82
        +---[0.012727ms] com.shinemo.common.tools.result.ApiResult:isSuccess() #83
        +---[0.009893ms] com.shinemo.common.tools.result.ApiResult:getData() #87

tt:官方名为时空隧道

watch可以排查函数的调用情况,比较适用在已知当次调用可能存在的情况后,查看信息。如果一个函数调用n次后,有几次为执行异常,我们要去找出这些异常的调用,在 watch中排查就不怎么方便了。使用 tt命令可以较方便查看异常的调用及信息

你对某方法开启tt后,会记录下每一次的调用(你需要设置最大监控次数),然后你可以在任何时候会看这里面的调用,包括出参,入参,运行耗时,是否异常等

tt -t com.shinemo.wangge.web.controller.todo.TodoController getTodoList -n 5

返回:

image-20200708142011105

image-20200708142043733

查看方法查看调用信息

tt -w '{method.name,params,returnObj,throwExp}' -x 3 -i 1000

重新触发一次

tt -p -i 1000

重新触发5次,每次间隔2秒

tt -p --replay-times 5 --replay-interval 2000 -i 1000

获取所有调用记录

tt -l

删除所有调用记录

tt --delete-all

stack:观察方法的调用路径

使用 stack命令查看方法的调用信息。

img

monitor:统计方法耗时

使用 monitor 命令监控统计方法的执行情况,比如指定时间内请求的总次数,成功次数,失败次数,平均响应时长,失败比例。

-c:表示统计周期,默认值为60秒

 monitor com.shinemo.wangge.web.controller.todo.TodoController getTodoList  -c 10

img

redefine:热更新

常用步骤

  1. 通过jad命令反编译,然后用vim来修改源码
  2. 通过mc命令来将修改后的代码编译为class文件
  3. 用redefine命令加载新的字节码文件
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
 
mc /tmp/UserController.java -d /tmp
 
redefine /tmp/com/example/demo/arthas/user/UserController.class

注意事项

  1. redefine的class不能修改、添加、删除类的field和method,包括方法参数、方法名称及返回值。
新增和修改,删除field,会抛异常:redefine error! java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)

新增方法会抛异常:redefine error! java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method

修改或删除方法会抛异常:redefine error! java.lang.UnsupportedOperationException: class redefinition failed: attempted to delete a method
  1. 正在跑的函数,没有退出不能生效,比如下面新增加的System.out.println,只有run()函数里的会生效
public class MathGame {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        MathGame game = new MathGame();
        while (true) {
    
    
            game.run();
            TimeUnit.SECONDS.sleep(1);
            // 这个不生效,因为代码一直跑在 while里
            System.out.println("in loop");
        }
    }
 
    public void run() throws InterruptedException {
    
    
        // 这个生效,因为run()函数每次都可以完整结束
        System.out.println("call run()");
        try {
    
    
            int number = random.nextInt();
            List<Integer> primeFactors = primeFactors(number);
            print(number, primeFactors);
 
        } catch (Exception e) {
    
    
            System.out.println(String.format("illegalArgumentCount:%3d, ", illegalArgumentCount) + e.getMessage());
        }
    }

异步保存请求到文件

1.使用&在后台执行任务

watch com.shinemo.wangge.web.controller.common.IndexController getIndex '{params,returnObj,throwExp}' -n 5 -x 3 '1==1' &  

这时命令会在后台执行,可以在console中继续执行其他命令。

使用jobs查看所有后台任务
使用kill杀死任务

2.使用>>将任务输出重定向

可通过>或者>>将任务输出结果输出到指定的文件中,可以和&一起使用,实现arthas命令的后台异步任务。比如:

#指定文件
watch com.shinemo.wangge.web.controller.common.IndexController getIndex '{params,returnObj,throwExp}' -n 5 -x 3 '1==1' >> test.out &

#不指定文件
watch com.shinemo.wangge.web.controller.common.IndexController getIndex '{params,returnObj,throwExp}' -n 5 -x 3 '1==1' >> &
默认会保存到~/logs/arthas-cache/${PID}/${JobId}

3.命令执行结果存日志

默认情况下,该功能是关闭的,如果需要开启,请执行以下命令:

options save-result true

命令的执行结果会异步保存在:{user.home}/logs/arthas-cache/result.log,请定期进行清理,以免占据磁盘空间。

4.退出arthas继续执行后台任务

如果不想停止arthas,继续执行后台任务,可以执行 quit 退出arthas控制台(stop 会停止arthas 服务)

Arthas支持Web Console

成功启动连接进城之后就已经自动启动,可以直接访问:http://localhost:8563/
页面上的操作模式和控制台完全一样.

Arthas 支持 管道命令

Arthas支持使用管道对上述命令的结果进行进一步的处理,如sm org.apache.log4j.Logger | grep

  • grep——搜索满足条件的结果
  • plaintext——将命令的结果去除颜色
  • wc——按行统计输出结果

Idea有arthas插件,非常方便好用.

猜你喜欢

转载自blog.csdn.net/kaihuishang666/article/details/107942092