诊断监控工具Arthas

在日常后端开发维护中, 一般都离不开一些很棘手的问题, 比如: 线下重现问题 ,线上出现了bug快速排查,整个系统的运行状态是什么样的?

而以往大家的做法一般都是手工看日志, 定位异常, 然后去改本地代码,侵入式的加日志, 非常的不优雅, 那随着Arthas 的开源, 我们是否可以更高效优雅的去做这些事情了呢? 一起来看看吧.

0x00. 简介

Arthas (阿尔萨斯)是今年阿里开源的一大亮点, 因为它功能刚需 , 强大易用的特点, 所以在开源几个月内一跃就成为Github上热门项目(目前12k+star), 以下是官方介绍的 Arthas 能做什么:

  1. 日志看到报错了, 不知道方法从哪个包加载?为什么会报各种类相关的 Exception?
  2. 改的代码为什么没有执行到?是没 commit?还是分支搞错了?
  3. 普通问题不方便线上 debug,只能加日志再重新打包发布, 完成后又删掉?
  4. 遇到线上业务问题,无法 debug,线下很难重现.
  5. 是否有一个全局视角来查看系统的运行状况?
  6. 有什么办法可以监控到JVM的实时运行状态?

看完还是很诱人的吧, 那长话短说, 上手试试效果吧. (后续如有更新版本, 请使用最新版的Arthas, 我在大版本更新的时候也会更新文章)

2.14号更新: arthas的3.10版已经更新, 大幅优化和新增了不少功能, 所以文章有过时的地方请联系更正, 并且由于3.10开始官方自带了一个在线体验的入门+ 进阶 实验环境, 相信已经非常易于上手了, 那文章之后就主要介绍一些实际的用例.

0x01. 上手

更新: 推荐先用官方提供的在线控制台(推荐)体验一下, 有两个小课程

采用官方推荐的方式: (两步启动)

1
2
3
4
5
6
wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

#这是demo项目,建议也下了启动,后续使用arthas监控它.
wget https://alibaba.github.io/arthas/arthas-demo.jar
java -jar arthas-demo.jar > demo.log & #建议后台

然后就可以看到arthas会自动调用jps (估计), 把当前所有jvm的进程都列出来, 供你attach选择. 看到彩色Logo说明attach成功, 进入交互命令界面.

arthas00

为了方便统一的展示, 这里就先采用官方的Demo ,后台启动之后, 用arthas关心一下它, 关心之前先得知道它在做什么. 代码就三步: 1.每秒生成一个随机数 2.做质数判断&分解 3.输出.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class MathGame {
private static Random random = new Random();
int illegalArgumentCount = 0; //累加器

public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
while (true) {
game.run(); //每隔1s执行一次,那可以观察一下阻塞的状态.
TimeUnit.SECONDS.sleep(1);}}

public void run() throws InterruptedException {
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());}}

public List<Integer> primeFactors(int number) { //素数(质因数)集合
if (number < 2) {
illegalArgumentCount++;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");}
List<Integer> result = new ArrayList<>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number = number / i;
i = 2;
} else {
i++;}}
return result;}}

public static void print(int number, List<Integer> primeFactors) {//输出结果
StringBuffer sb = new StringBuffer(number + "=");
for (int factor : primeFactors) {sb.append(factor).append('*');}
if (sb.charAt(sb.length() - 1) == '*') {sb.deleteCharAt(sb.length() - 1);}
System.out.println(sb);}

概览可以通过dashboard -i 20000 命令来20s刷新一次查看, 默认5s, 线程采样是100ms每次

arthas00

直接看整体情况是为了更好的去细化问题, 比如我们想看看当前100% 占用的线程Timer在做什么, 通过thread 1 可以查看java线程号为1的—-main() 的状态, 以及更强大的代码查看.

arthas00

然后具体看某个类/方法的实时状况, 可以使用monitorwatch/stack 命令. 先看看monitor demo.MathGame * -c 5 代表5s间隔输出这段时间内MathGame类的所有方法执行次数, 成功/异常 ,以及执行时间等常见信息, 这是外部层面的

arthas00

然后如果想要细节到方法的输出输出信息, 类似用watch demo.MathGame run '#cost>10' ,如果想详细跟踪函数的每步调用(就像断点一样), 可以用trace *MathGame print (-j可跳过JDK方法输出), 你可以看到一个详细的调用次序和时间. 可谓异常强大… (以前我们是人工打点..)

arthas00

0x02.进阶使用(重要)

上面是最常规的基本用法, 很多人可能觉得, 完全没有达到最开始说的, 我想查看某个方法, 怎么不侵入的方法加日志呢? 这好像只是一些监控和trace ,没有涉及debug的地方, 那接下来重点说说Debug 方式.. 这里有很多的高级技巧, 灵活使用可以使你调试能力有质的提高, 并且非常优雅和强大.

3.1更新: 这里同样有一个官方给的挺好的例子, 是一个SpringBoot 的demo ,参考进阶篇. (建议仔细阅读体验整个教程.)

1. 热修改代码

下面是最常用的热修改的三板斧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
wget https://github.com/hengyunabc/katacoda-scenarios/raw/master/demo-arthas-spring-boot.jar
#可以不后台运行,但是注意有可能需要sudo权限,不然网络权限不足
sudo java -jar demo-arthas-spring-boot.jar &

#进入arthas之后选择spring-boot
#jad --> mc --> redefine 三部曲, 第一个反编译Java文件,第二个修改后生产class,第三加载
#1.反编译输出
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java

#2.搜索到对应类的classloader
sc -d *UserController |grep classLoader

#3.修改后编译,传入正确的code,不可省
mc -c classLoader /tmp/UserController.java -d /tmp

#4.重新热加载(无需重启服务),并且非侵入, 只是临时修改.
redefine /tmp/com/example/demo/arthas/user/UserController.class

总的来说, 这套jad --> mc --> redefine三板斧在大部分情况下调试可以说是非常的优雅, 高效的实现了字节码热更新技术, 少数反编译失败的可自己把本地文件传到服务器, 可以说是非常必要掌握的一个诊断方式, 告别那种臃肿,.侵入的写TODO .打包然后上传替换吧~

补充: 三板斧虽然很好用, 但需要注意有一些特殊情况, 还是要手工DIY一下, 首先很大的class可能不好反编译或者有奇怪的问题, 这时候建议手动上传, 而不是死磕jad (如果发现反编译没有响应, 先确认这个class已被jvm加载….)

2. 定位报错

很多场景下, 比如你直接访问一个网页, 它报了500/400/401 等等错误, 你会陷入一个两难的局面, 你从客户端并不能获得更多的信息, 去服务端打断点还需要重启服务器, 也很麻烦, 我就想快速知道报错的信息和调用栈, 那就用到监控三兄弟了: watch + trace + monitor (上面也有简单说过)

举个具体的例子, 我在Server端添加了一个新的接口, 然后从postman 发送了一个post请求, 然后Server端提示500, 有空指针异常, 我想知道具体是什么为空, 整个调用栈到哪卡住了, 如何做呢:

1
2
3
4
5
6
#1. watch确认为空的类,假设我修改了A类.但是不知道是哪个方法
# 一般观察方法的入参值,返回值,抛出异常三个常用参数就行了
watch xx.xx.A * '{params,returnObj,throwExp}' -x 2 #展开2层异常栈, 更具体常用3层. 你会发现打印的信息相当详细

#2. 一般watch就能秒杀500报错这种问题了,trace能帮我们把时间消耗用树形输出, 就会更清晰,而且会告诉你哪抛出了异常.
trace xx.xx.A * -j #不输出JDK方法,当然你可以不用*, 而是通过watch之后更小的范围.

然后你知道了报错的根源或者耗时长的地方, 就再利用热修改的方式加自定义的临时记录来观察和调整就能解决许多以前很难排查的问题了.

下面补充的是进一步的进阶, 但是ognl真的写的很少…. 这里估计是arthas 相对门槛最高的地方, 但是一般来说参考官方的几个用法就很够用了, 其他就慢慢积累吧…

3. 常用的正则和通配符

X. ognl表达式

建议先参考作者的文档– 活用ognl表达式 ,

0x03.实际案例

上手用的是一个很简单的例子, 这里开始用一个实际的案例和环境来进行说明, 当然也建议先参考一下已有的case(文尾参考资料有附)

先看看0x02 的demo, 尝试一下其实就会核心了, 其他复杂的可以参考官方issue先.

0x0n. 常见问题

1.watch的时候提示3658端口占用.

这个比较常见, 是因为你之前选择watch的进程没有释放, 需要先选之前的进程, 进入后shutdown ,然后重新连接新的进程.

N-1. Thread面板里的进程状态含义

这其实是单独的一个大话题, 而且包括OS自己的进程态和Java的线程态. 这里是有一些地方比较有争议,的 ,所以先引用一张总结图. 简单有个串通:

arthas00

N.一些常见优化配置

  1. (过时)默认session超时5分钟太短, 改为30min也很简单, 多看一下命令help : java -jar arthas-boot.jar --session-timeout 1800 (更新: 最新3.10版已经默认改为30min)

参考资料:

  1. 官方issue页的使用case分享
  2. 结合IDEA使用arthas进行远程断点Debug