后端
推荐! 阿里开源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
- 使用 as.sh
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 方法内部调用路径,并输出方法路径上的每个节点上耗时 **
观察方法执行的时候哪个子调用比较慢:

-
*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

-
Profiler/FlameGraph/火焰图
$ profiler start Started [cpu] profiling $ profiler stop profiler output file: /tmp/demo/arthas-output/20191125-135546.svg OK通过浏览器查看 profiler 结果:

-
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 '服务的名称'

2. 启动 arthas-boot.jar 工具
$ java -jar arthas-boot.jar '第一步获取到的 pid'

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

7. Dashboard 仪表盘

8. WebConsole

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