arthas
介绍
Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。
当你遇到以下类似问题而束手无策时,Arthas可以帮助你解决:
这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception?
我改的代码为什么没有执行到?难道是我没 commit?分支搞错了?
遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗?
线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现!
是否有一个全局视角来查看系统的运行状况?
有什么办法可以监控到JVM的实时运行状态?
Arthas采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
基操
下载
curl -O https://alibaba.github.io/arthas/arthas-boot.jar
启动
java -jar arthas-boot.jar
???官网给出的启动方案为啥不好使呢???
原来启动arthas时需要指定一个可用的java进程的pid,如下启动命令
sudo java -jar arthas-boot.jar XXXX
如果不是root权限,最好使用sudo,要不可能会报没有权限的错误
启动成功后就进入到了arthas的控制台了,你可以·使用help查看帮助
监控方法出入参
watch com.yonyou.einvoice.inputtax.vatcheck.controller.VatCheckController gxcx ‘{params, returnObj}’
这里使用watch命令对类名为VatCheckController,方法名为gxcx的方法做监控,监控的内容是入参和出参
然后对接口进行访问。arthas后端打印数据如下:
ts=2020-04-26 14:59:54; [cost=461.888126ms] result=@ArrayList[
@Object[][isEmpty=false;size=5],
@TotalCommonResponse[com.yonyou.einvoice.common.response.TotalCommonResponse@b4d4810],
]
发现如果我们参数是一个对象的话,根本看不出来参数内容是什么,这不符合我们的预期,需要在命令后加-x 3
-x指的是打印内容的层级,如果不设置默认是1,就只会打印出对象的内容,详细信息不会打印,使用命令
watch com.yonyou.einvoice.inputtax.vatcheck.controller.VatCheckController gxcx ‘{params, returnObj}’ - x 3
之后,再对接口进行访问,arthas后端打印数据如下:
ts=2020-04-26 15:05:10; [cost=95.735133ms] result=@ArrayList[
@Object[][
@GxcxQuery[
nsrsbh=@String[ZXCASDQWE],
startDate=@String[2020-01-01],
endDate=@String[2020-04-26],
fpdm=@String[],
fphm=@String[],
xsfNsrsbh=@String[],
xsfMc=@String[],
fplx=@String[],
fpzt=@String[0],
orgIds=@ArrayList[isEmpty=false;size=16],
projectIds=null,
corpid=@String[61c01ed1-4476-4c1f-b99b-a4a61bcee980],
vnote=@String[],
verifyStatus=@Integer[2],
reimburseStatus=@ArrayList[isEmpty=true;size=0],
invoiceStatus=@ArrayList[isEmpty=true;size=0],
verifyAll=null,
ygxVerifyAll=@ArrayList[isEmpty=false;size=2],
precheckStatusSmartAll=@ArrayList[isEmpty=true;size=0],
smart=@String[1],
purchaseAccount=@Boolean[false],
invComplete=@Boolean[false],
invAccount=@Boolean[false],
ygxRecord=@Boolean[false],
byPage=@String[Y],
period=null,
accountUser=null,
accountStart=null,
accountEnd=null,
ygxPerson=null,
ygxUserName=@String[],
ygxStatus=null,
gxType=@Integer[1],
globalAllMode=@Boolean[false],
ygxUserIds=@ArrayList[isEmpty=true;size=0],
],
null,
null,
@Integer[1],
@Integer[15],
],
@TotalCommonResponse[
code=@String[0000],
msg=@String[查询成功],
datas=null,
totalCount=@Long[0],
],
]
想要的数据基本都打印出来了,非常好~
使用该功能可以很快定位出线上异常时,方法的入参和出参。相当于动态添加了日志。
热更新代码
通过jad/mc/redefine 命令实现动态更新代码的功能。
目前,访问 http://localhost/user/0 ,会返回500异常:
curl http://localhost/user/0
{“timestamp”:1550223186170,“status”:500,“error”:“Internal Server Error”,“exception”:“java.lang.IllegalArgumentException”,“message”:“id < 1”,“path”:"/user/0"}
下面通过热更新代码,修改这个逻辑。
jad反编译UserController
jad --source-only com.example.demo.arthas.user.UserController > /tmp/UserController.java
–source-only 指定的类名需要写全路径
jad反编译的结果保存在 /tmp/UserController.java文件里了。
再打开一个Terminal 3,然后用vim来编辑/tmp/UserController.java:
vim /tmp/UserController.java
比如当 user id 小于1时,也正常返回,不抛出异常:
@GetMapping(value={"/user/{id}"})
public User findUserById(@PathVariable Integer id) {
logger.info("id: {}", (Object)id);
if (id != null && id < 1) {
return new User(id, "name" + id);
// throw new IllegalArgumentException("id < 1");
}
return new User(id.intValue(), "name" + id);
}
sc查找加载UserController的ClassLoader
sc -d *UserController | grep classLoaderHash
$ sc -d *UserController | grep classLoaderHash
classLoaderHash 1be6f5c3
可以发现是 spring boot LaunchedURLClassLoader@1be6f5c3 加载的。
mc编译java代码
保存好/tmp/UserController.java之后,使用mc(Memory Compiler)命令来编译,并且通过-c参数指定ClassLoader:
mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
$ mc -c 1be6f5c3 /tmp/UserController.java -d /tmp
Memory compiler output:
/tmp/com/example/demo/arthas/user/UserController.class
Affect(row-cnt:1) cost in 346 ms
redefine重新加载class
再使用redefine命令重新加载新编译好的UserController.class:
redefine /tmp/com/example/demo/arthas/user/UserController.class
$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1
热修改代码结果
redefine成功之后,再次访问 http://localhost:8080/user/0 ,结果是:
{
“id”: 0,
“name”: “name0”
}
该功能可以快速的修复线上的小问题,对于类文件比较大的情况,可以把java文件拉到本地修改后替换,不用在linux上vim,有木有很好用啊
感觉arthas就是线上小问题定位及复的神器啊
arthas还有个idea的插件,有了插件,就可以很方便的复制命令了
idea插件使用说明
- 找到需要监控的方法
- 在方法上右键找到Arthas Command -> Watch
- 命令已经复制上了
- 在arthas命令行Ctrl+V即可粘贴
使用中遇到的小麻烦
- 在arthas运行过程中选择监控的进程突然重启,或宕掉,arthas进程会自动退出
- 进程重启后,再次进入arthas监控该进程会报一个错误
- 最开始以为进程没kill掉,看了一下也没有进程
- 网上找了好久也没找到正确的答案
[ERROR] The telnet port 3658 is used by process 6178 instead of target process 12881, you will connect to an unexpected process.
[ERROR] 1. Try to restart arthas-boot, select process 6178, shutdown it first with running the 'stop' command.
[ERROR] 2. Or try to stop the existing arthas instance: java -jar arthas-client.jar 127.0.0.1 3658 -c "stop"
[ERROR] 3. Or try to use different telnet port, for example: java -jar arthas-boot.jar --telnet-port 9998 --http-port -1
- 最终在错误提示中找到了答案
- 执行命令:java -jar arthas-client.jar 127.0.0.1 3658 -c “stop” 停掉之前的进程,再次重启arthas就可以使用了