线上问题快速定位神器arthas

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插件使用说明

  1. 找到需要监控的方法
  2. 在方法上右键找到Arthas Command -> Watch
  3. 命令已经复制上了
  4. 在arthas命令行Ctrl+V即可粘贴
    在这里插入图片描述

使用中遇到的小麻烦

  1. 在arthas运行过程中选择监控的进程突然重启,或宕掉,arthas进程会自动退出
  2. 进程重启后,再次进入arthas监控该进程会报一个错误
  3. 最开始以为进程没kill掉,看了一下也没有进程
  4. 网上找了好久也没找到正确的答案
[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

  1. 最终在错误提示中找到了答案
  2. 执行命令:java -jar arthas-client.jar 127.0.0.1 3658 -c “stop” 停掉之前的进程,再次重启arthas就可以使用了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值