推荐! 阿里开源Java在线诊断工具 Arthas

0x00 Arthas 简介 当你遇到线上环境调试接口的时候,你是否还是通过 Postman 请求一下接口再去后台看一下日志, 操作调试极其繁琐;或者当你遇到以下类似问题而束手无策的时...

0x00 Arthas 简介

当你遇到线上环境调试接口的时候,你是否还是通过 Postman 请求一下接口再去后台看一下日志, 操作调试极其繁琐;或者当你遇到以下类似问题而束手无策的时候:

  • 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
  • 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
  • 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
  • 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
  • 是否有一个全局视角来查看系统的运行状况?
  • 有什么办法可以监控到 JVM 的实时运行状态?
  • 怎么快速定位应用的热点,生成火焰图?

Arthas 协助你解决这些问题, 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。

0x01 快速开始

  • 使用 arthas-boot(推荐)

下载arthas-boot.jar,然后用java -jar的方式启动:

$ curl -O <https://arthas.aliyun.com/arthas-boot.jar>
$ java -jar arthas-boot.jar 'pid'

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

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

Arthas 支持在 Linux/Unix/Mac 等平台上一键安装,请复制以下内容,并粘贴到命令行中,敲 回车 执行即可:

curl -L <https://arthas.aliyun.com/install.sh> | sh

上述命令会下载启动脚本文件 as.sh 到当前目录,你可以放在任何地方或将其加入到 $PATH 中。

直接在 shell 下面执行./as.sh,就会进入交互界面,也可以执行./as.sh -h来获取更多参数信息。

0x02 实战使用

  • jad 对类进行反编译
$ jad javax.servlet.Servlet

ClassLoader:
+-java.net.URLClassLoader@6108b2d7
  +-sun.misc.Launcher$AppClassLoader@18b4aac2
    +-sun.misc.Launcher$ExtClassLoader@1ddf84b8

Location:
/Users/xxx/work/test/lib/servlet-api.jar

/*
 * Decompiled with CFR 0_122.
 */
package javax.servlet;

import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public interface Servlet {
    public void init(ServletConfig var1) throws ServletException;

    public ServletConfig getServletConfig();

    public void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletInfo();

    public void destroy();
}

  • *trace 方法内部调用路径,并输出方法路径上的每个节点上耗时 **

    观察方法执行的时候哪个子调用比较慢:

    https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/trace.png

  • *watch 方法执行数据观测 (重要) **

    观察方法 test.arthas.TestWatch#doGet 执行的入参,仅当方法抛出异常时才输出。

    $ watch test.arthas.TestWatch doGet {params[0], throwExp} -e
    Press Ctrl+C to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 65 ms.
    ts=2018-09-18 10:26:28;result=@ArrayList[
        @RequestFacade[org.apache.catalina.connector.RequestFacade@79f922b2],
        @NullPointerException[java.lang.NullPointerException],
    ]
    
    
  • Monitor 方法执行监控

    监控某个特殊方法的调用统计数据,包括总调用次数,平均 rt,成功率等信息,每隔 5 秒输出一次。

    $ monitor -c 5 org.apache.dubbo.demo.provider.DemoServiceImpl sayHello
    Press Ctrl+C to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 109 ms.
     timestamp            class                                           method    total  success  fail  avg-rt(ms)  fail-rate
    ----------------------------------------------------------------------------------------------------------------------------
     2018-09-20 09:45:32  org.apache.dubbo.demo.provider.DemoServiceImpl  sayHello  5      5        0     0.67        0.00%
    
     timestamp            class                                           method    total  success  fail  avg-rt(ms)  fail-rate
    ----------------------------------------------------------------------------------------------------------------------------
     2018-09-20 09:45:37  org.apache.dubbo.demo.provider.DemoServiceImpl  sayHello  5      5        0     1.00        0.00%
    
     timestamp            class                                           method    total  success  fail  avg-rt(ms)  fail-rate
    ----------------------------------------------------------------------------------------------------------------------------
     2018-09-20 09:45:42  org.apache.dubbo.demo.provider.DemoServiceImpl  sayHello  5      5        0     0.43        0.00%
    
    
  • Web Console

    https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/web-console-local.png

  • Profiler/FlameGraph/火焰图

    $ profiler start
    Started [cpu] profiling
    $ profiler stop
    profiler output file: /tmp/demo/arthas-output/20191125-135546.svg
    OK
    
    

    通过浏览器查看 profiler 结果:

    https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/arthas-output-svg.jpg

  • Arthas Spring Boot Starter

    <dependency>
        <groupId>com.taobao.arthas</groupId>
        <artifactId>arthas-spring-boot-starter</artifactId>
        <version>${arthas.version}</version>
    </dependency>
    
    

    应用启动后,spring 会启动 arthas,并且 attach 自身进程。具体请查看文档: https://arthas.aliyun.com/doc/spring-boot-starter.html

更多命令请前往官方文档查看: (https://arthas.aliyun.com/doc/commands.html)[https://arthas.aliyun.com/doc/commands.html]

0x03 实战案例

数据联调过程中客户端只是收到后端的提示 参数错误 但是无法确认具体哪个参数异常,后端同学在查询日志时也未见到明显的 error 日志。

1. 查看服务运行的 pid

$ ps -aux | grep java | grep '服务的名称'

https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/image-20210412093702279.png

2. 启动 arthas-boot.jar 工具

$ java -jar arthas-boot.jar '第一步获取到的 pid'

https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/image-20210412094016999.png

3. 使用 watch 查看 ExampleController 的 inputId 的调用参数和响应数据

$ watch 类全路径名 方法名 "{params,returnObj}" -x 参数遍历层级 -b 观察方法入参数

# 观察方法出参和返回值
[arthas@2423]$ watch com.example.arthas.controller.ExampleController inputId '{params,returnObj}' -x 2
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 27 ms, listenerId: 7
method=com.example.arthas.controller.ExampleController.inputId location=AtExit
ts=2021-04-12 09:50:27; [cost=0.135811ms] result=@ArrayList[
    @Object[][
        @String[12],
    ],
    @String[12],
]

# 观察方法入参
[arthas@2423]$ watch com.example.arthas.controller.ExampleController inputId '{params,returnObj}' -x 2 -b
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 27 ms, listenerId: 8
method=com.example.arthas.controller.ExampleController.inputId location=AtEnter
ts=2021-04-12 09:56:38; [cost=0.028981ms] result=@ArrayList[
    @Object[][
        @String[12],
    ],
    null,
]

4. 通过请求参数过滤不需要的请求

# 只有参数为 '12' 时才会输出请求
[arthas@2423]$ watch com.example.arthas.controller.ExampleController inputId '{params,returnObj}' "params[0]=='12'" -x 3 -b
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 27 ms, listenerId: 9
method=com.example.arthas.controller.ExampleController.inputId location=AtEnter
ts=2021-04-12 10:02:17; [cost=0.023233ms] result=@ArrayList[
    @Object[][
        @String[12],
    ],
    null,
]

5. 查看一个 class 类的源码信息

$ jad com.example.arthas.controller.ExampleController

ClassLoader:
+-org.springframework.boot.loader.LaunchedURLClassLoader@238e0d81
  +-sun.misc.Launcher$AppClassLoader@42a57993
    +-sun.misc.Launcher$ExtClassLoader@579bb367

Location:
file:/Users/lumo/Downloads/arthas-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/

       /*
        * Decompiled with CFR.
        *
        * Could not load the following classes:
        *  com.example.arthas.data.User
        *  org.springframework.web.bind.annotation.GetMapping
        *  org.springframework.web.bind.annotation.PostMapping
        *  org.springframework.web.bind.annotation.RequestBody
        *  org.springframework.web.bind.annotation.RequestMapping
        *  org.springframework.web.bind.annotation.RequestParam
        *  org.springframework.web.bind.annotation.RestController
        */
       package com.example.arthas.controller;

       import com.example.arthas.data.User;
       import org.springframework.web.bind.annotation.GetMapping;
       import org.springframework.web.bind.annotation.PostMapping;
       import org.springframework.web.bind.annotation.RequestBody;
       import org.springframework.web.bind.annotation.RequestMapping;
       import org.springframework.web.bind.annotation.RequestParam;
       import org.springframework.web.bind.annotation.RestController;

       @RestController
       @RequestMapping(value={"/example"})
       public class ExampleController {
           @GetMapping
           public String inputId(@RequestParam(value="id") String id) {
/*24*/         System.out.println("id: " + id);
/*25*/         return id;
           }

           @PostMapping
           public String inputUser(@RequestBody User user) {
/*30*/         System.out.println("id: " + user.toString());
/*31*/         return user.toString();
           }
       }

Affect(row-cnt:1) cost in 405 ms.

6. 测试方法的性能

$ monitor -c 5 com.example.arthas.controller.ExampleController inputId

https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/image-20210412101311923.png

7. Dashboard 仪表盘

https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/image-20210412100702729.png

8. WebConsole

https://j3dream-1303029773.cos.ap-guangzhou.myqcloud.com/img/image-20210412101745903.png

了解更多请参考官方网站: [README] [官网(https://arthas.aliyun.com/)]