还在为 Arthas 命令头疼? 来看看这个插件吧!

1,622 阅读17分钟

世界上最快的捷径,就是脚踏实地,本文已收录【架构技术专栏】关注这个喜欢分享的地方。

一、背景

爱上Java诊断利器Arthas之Arthas idea plugin 前世今生,arthas 对于小哥来说,已经不可分割了,在我的日常开发、线上问题排查中扮演了非常重要的角色。

作为小开发的我,日常需要排查线上运营同学提的各种bug、各种线上问题诊断、日常运维、线上问题优化等等一堆的问题。在刚刚来公司前期是比较恐惧运维任务的,代码不熟悉、各种问题比较多...几乎崩溃的状态,运维的一周基本上没有干活,完全是全投入到运维的任务中,排查问题效率低下,由于深刻体验这这种奔溃,一直想改变这种状态,直到arthas的开源,让我在这种崩溃的状态中减轻了不少负担...同时也让我成为了同事们咨询arthas 排查问题的小帮手

虽然使用arhtas 特别的方便,但是在此过程中也遇到一些问题,作为问题咨询小帮手也感到有点不方便,因此才造就了arthas idea 插件的诞生。

目前Arthas 官方的工具还不够足够的简单,需要记住一些命令,特别是一些扩展性特别强的高级语法,比如ognl获取spring context 为所欲为,watch、trace 不够简单,需要构造一些命令工具的信息,因此只需要一个能够简单处理字符串信息的插件即可使用。

当在处理线上问题的时候需要最快速、最便捷的命令,因此Idea arthas plugin插件还是有存在的意义和价值的。---这个是最初编写这个插件的最核心的理由。

1.1 插件下载地址

plugins.jetbrains.com/plugin/1358…

1.2 github 地址

github.com/WangJi92/ar… 缺星星~

user-case:github.com/WangJi92/ar…

使用文档:www.yuque.com/docs/share/…

1.3 本文使用demo 地址

github.com/WangJi92/ar…

本文视频介绍大约 1hour,最好结合视频+文档学习+实践

优酷地址:v.youku.com/v_show/id_X…

哔哩哔哩 :www.bilibili.com/video/BV1K7…

1.4 相关功能

二、 自问自答

2.1 你有在使用arthas ?你们公司怎么使用的?

对啊,我们的公司经常使用,在预发环境中使用arhas 排查问题、在测试环境使用arhtas 排查问题、性能调优参考。

直接通过docker file中打包加入一个as.sh的shell 其实就是java -jar arthas-boot.jar,开发就可以执行as.sh启动脚本启动arthas 非常的方便,对于开发非常的友好,不需要了解细节问题。

2.2 你在学习arthas的过程,有过困惑?

对啊,我非常的困惑,arthas 提供的功能特别的多,跟着在线上的教程学习过程中非常爽,但是时间久了就记不住,一些复杂的场景需要打开小本本慢慢的看,然后一步步的处理,中间使用起来没有连续性。

特别是高级的ognl 语法,没有仔细的学习一些特性真的很难搞懂,要想用得好,还得非常深入的学习,小本本得记住一些常用的功能。不然线上处理问题,需要速度...根本节奏跟不上。

2.3 作为小帮手,同事都问你啥?

没有特别多的,arthas的底层代码看了一点点,没有看完,基本的功能理解了太深入我也回答不上来,一般都是一些基础的功能,比如 怎么我想获取这个变量怎么使用?

这个问题怎么用arhtas 排查呢?trace 一下?我想这个怎么办呢?

这里得到一个结论:并不是每个人都是非常的关注arthas,对于arthas 的语法糖的使用并不是很care,我仅仅想快速的解决问题,一般就几个常用的功能即可,我就想用一个简单的功能,还要我记住这么多?这么麻烦,还不如打个log???

2.4 我在本地如何快速启动 arthas?

是否和我一样的迷茫?每次都有下载一个arthas-boot.jar ?

其实我仅仅需要一个记住的命令即可,这里不是docker环境,是你本地的开发环境,我是如何解决的呢?小本本记录下来,复制粘贴(如果测试环境docker 中没有arhtas 粘贴执行一遍),通过alias 全局的记录一个命令 as.sh 启动即可,我都忘记了arthas 如何启动的......程序员都讨厌重复的劳动力。

curl -sk https://arthas.gitee.io/arthas-boot.jar -o ~/.arthas-boot.jar && echo "alias as.sh='java -jar ~/.arthas-boot.jar --repo-mirror aliyun --use-http'" >> ~/.bashrc && source ~/.bashrc

三、Arthas idea plugin 前世今生

3.1 使用前期

本人有个习惯,就是特别喜欢把东西通过文档的形式分享出来,在公司的内部分享了很多的文档,有的是技术文档、有的是使用文档、有的是记录文档等等,测试环境中docker 没有安装arthas ,又特别想使用arthas 嗯,那么按照上上面的bash 执行一下命令即可使用。

比如安装脚本、watch 脚本、trace 脚本,下面的基本上就像我在公司内部编写的一个样子。

使用前期基本上这几个命令是最常用的命令,其他的都没有使用。

就这样分享我的文档,构建命令排查问题的时候先去按照这个命令copy reference 然后到vscode 上面进行编辑修改,然后到服务器上启动arthas 执行命令 一直这个样子。

安装脚本

javacurl -sk https:*//arthas.gitee.io/arthas-boot.jar -o ~/.arthas-boot.jar && echo "alias as.sh='java -jar ~/.arthas-boot.jar --repo-mirror aliyun --use-http'" >> ~/.bashrc && source ~/.bashrc*

watch 查看入参返回值

watch 查看添加一些默认参数,比如-x 3 展开3层数据,估计部分同学不知道,打印参数值,返回值、异常{params,returnObj,throwExp},嗯你按照这个格式将右键copy refence #号去掉替换一下即可。


watch com.wangji92.arthas.plugin.demo.controller.StaticTest invokeStaticMethodParamObjListUser '{params,returnObj,throwExp}' -n 5 -x 3

trace 查看调用链

备注:这个只会对于当前方法生效,不会调用方法中调用的方法。

如果要再调用下一个方法使用

trace -E xxxAclass|xxxBClass xxMethod|xxx2Method 这样的规则 没有先后顺序

不跳过jdk的方法 --skipJDKMethod false

trace com.wangji92.arthas.plugin.demo.controller.StaticTest invokeStaticMethodParamObjListUser -n 5

退出

shutdown or stop

相关链接

快速入门:arthas.gitee.io/quick-start…

进阶使用:arthas.gitee.io/advanced-us…

用户案列:github.com/alibaba/art…

3.2 使用中期

由于没事的时候逛逛arhtas github issue. 受到arthas ognl强大的影响,我开始关注这些特殊的用法了,对我的影响蛮大的,特别是获取spring context 为所欲为。然后小本本开始记录了,记录了这些相关的链接,在部门群开始分享,但是感觉关注度不行,由于这个还是有点复杂,难于记忆,还要认真的学习一下ognl 官方的文档,非常重要,了解其特性。

watch 使用ognl 执行表达式、tt 时间隧道也是使用ognl 执行表达式,ognl 肯定也是,这样给与了很多强大的想想空间,ognl 语法十分强大。

这样就开始了解执行静态方法、静态变量、调用方法等等,同时还扩展了通过ognl 获取静态的spring context (模仿了横云断岭大神的tt 获取spring context)然后调用方法、调用字段信息。还在小本本中记录一些场景的语法糖。

那个时候真希望解决问题的时候语法糖就在脑海中哈哈,可惜我打字不够快....

ognl static 调用字段、方法

@xxxclass@xxxmethod ,语法class 加载的classloder不一样,这里静态值也可能不一样,因此先要获取一下classloader的hash值,由于默认获取的是系统的classloader,由于这个关系,很多伙伴问我,so 按照教程来学习咯。

  • 先获取classloader hash 值
sc -d com.wangji92.arthas.plugin.demo.controller.StaticTest

[arthas@4308]$ sc -d com.wangji92.arthas.plugin.demo.controller.StaticTest

class-info com.wangji92.arthas.plugin.demo.controller.StaticTest

code-source /Users/wangji/Documents/project/arthas-plugin-demo/target/classes/

name com.wangji92.arthas.plugin.demo.controller.StaticTest

isInterface false

isAnnotation false

isEnum false

isAnonymousClass false

isArray false

isLocalClass false

isMemberClass false

isPrimitive false

isSynthetic false

simple-name StaticTest

modifier public

annotation

interfaces

super-class +-java.lang.Object

class-loader +-org.springframework.boot.devtools.restart.classloader.RestartClassLoader@316bc132

+-sun.misc.Launcher$AppClassLoader@18b4aac2

+-sun.misc.Launcher$ExtClassLoader@4c6e276e

classLoaderHash 316bc132
ognl -x 3 '@com.wangji92.arthas.plugin.demo.controller.StaticTest@INVOKE_STATIC_NAME' -c 316bc132

静态static spring context 调用方法、字段

由于受到"Alibaba Arthas实践--获取到Spring Context,然后为所欲为 :github.com/alibaba/art…" 这篇文章的影响,对于获取到spring context的强大感到十分的意外、甚至惊喜。

但是这个使用起来通过tt 整个流程还是比较复杂,传递给同事估计不熟悉接收的程度十分的低下,需要进行两次才能进行调用,还是复杂,为何不能减少一次呢?我们的统一的jar包中有统一的一个静态的spring context :www.dcalabresi.com/blog/java/s…

由于这个spring context 是静态的,那么一定是可以通过ognl 获取的,然后调用bean 就可以为所欲为了,进行各种惊喜操作。流程类似上面这个,不过需要小本本记录 自己按照格式进行替换想调用哪个bean的方法、字段。

public class ApplicationContextProvider implements ApplicationContextAware {
    
    private static ApplicationContext context;
 
    public ApplicationContext getApplicationContext() {
        return context;
    }
 
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        context = ctx;
    }
}

获取commonController这个bean 的这个方法进行调用

ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getBean("commonController").getRandomInteger()' -c 316bc132

ognl 语法糖

如何使⽤arthas ognl 调⽤⽅法?⼀般在我们后端使⽤的都是spring 管理的,只需要获取Spring context即可操作处理,对于传递对象参数,传递Map ,传递List。ognl按照表达式的逗号分隔的顺序执⾏命令,⽀持链式调⽤,具体可以参 ognl 实践:blog.csdn.net/u010634066/…

Arthas的一些特殊用法文档说明:github.com/alibaba/art…

ognl 语法 #开头变量 在ognl 里面是变量全局的,可以传递 ;字段类型要注意 Doubble -> 1D,Long - >1L ,String ->'', class对象->@xxxClass@class 相当于一个静态的变量

List 参数
#listObject={"a","b"}

Lists.newArrayList("a","b")
map 参数
#mapObject= #{"key","value"}

Map<String,String> map = new HashMap();
map.put("key",value);

array 参数
#array=new java.lang.String[]{"1"}

String[] array = new String[1];
array[0]="1";
复制代码


obj 参数

调用构造函数

#obj = new xxxxClass();

xxxxClass obj = new xxxxClass();
Class 对象参数
ognl  -x  3  '@com.wangji92.arthas.plugin.demo.controller.StaticTest@invokeClass(@com.wangji92.arthas.plugin.demo.controller.StaticTest@class)' -c 482503f0

public static String invokeClass(Class classN) {
  if (classN == null) {
     return "";
  }
  return classN.getName();
}

 

复杂对象构造

由于 #user=new com.wangji92.arthas.plugin.demo.controller.User(),构造了一个全局的ognl值,对于当前执行的ognl 链中,因此可以在最后的使用,其他的场景就可以自由的发挥了。

#user.setName("wangji") 设置参数的值,然后放置在List的参数中,invokeStaticMethodParamObjListUser(0,{#user,#user}) 这种复杂的场景,真的需要很理解ognl的语法知识点。

不然完全懵了。认真看arthas userCase 中有很多的东西、干货。

ognl  -x  3  '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),@com.wangji92.arthas.plugin.demo.controller.StaticTest@invokeStaticMethodParamObjListUser(0,{#user,#user})' -c e374b99

public static String invokeStaticMethodParamObjListUser(Integer number, List<User> names) {
       if (number == null) {
           number = 1;
       }
       if (names == null) {
           names = Collections.EMPTY_LIST;
       }
       return number + names.toString();
}

期间小插曲(spring shell)

在这个使用的过程中,深刻的烦恼着copy 这些语法糖,但是使用最多的任然是trace watch ,一般情况下都是那几个默认的参数即可,想着能不能够自动化一下子,同事经常使用scala的交互式命令写了一些场景中常见的命令,这个arthas的东西能不能也这样子呢?好像是可行的。

这个也用了一段时间,挺好用的哈哈,直到有天看到了一个博客介绍编写IDEA 插件,一语惊醒梦中人,为何不把这个中间的过程去掉呢?支持更多的功能,让这个功能更加的流畅起来呢?

简单的功能需要有,如果就是现在的at、aw、as 估计我不会去想着学习编写一个插件的,因为我还想着更多的自动化,比如 static -c 参数能不能连续性?static spring context 能不能连续性?固话下来呢?一些思考,和小伙伴讨论了一下子,就在周末学习学习开动了起来。

IDEA->Copy Refence (剪切板)->Spring shell 命令行解析字符串 -> 转换成arthas的语法糖

为此还去学习了一下子spring shell的相关文档,记录一下学习遇到的问题 ,还写了一篇博客介绍命令行中收集了我学习的一些工具 参考链接 Java程序员的命令行工具-spring shell 命令行,Arthas 线上运维

arthas
        ai, arthasInstall: arthas 安装脚本
        arthasStatic, as: 获取类的静态字段,变量(-c classLoaderHash 默认值 SystemClassLoader)[ sc -d com.test 获取 classLoaderHash 值]
        arthasTable: arthas 学习(各种特殊链接)
        arthasTrace, at: trace 某个方法的调用栈
        arthasWatch, aw: watch 某个方法的入参返回值
 

3.3 使用后期

后期由于开发arthas idea plugin 插件的功能愈来愈具备连贯性、简化性 基本上简单的通过文档学习一下,实操一遍之后不会忘记如何使用,右键点开即可使用。

如今的功能小编在不断的跟进着arthas 官方的命令,将一些有可能集成进来的功能都进行了集成,还集成了一些高级的功能哈,有好滴idea 可以通过github 联系我,进行处理。

最近为了参与arthas的征文活动,写了一个github arthas idea plugin 的demo,也是本文的一个重要的demo,后续的实践都是跟着这个demo 进行实践 :github.com/WangJi92/ar…

四、arthas idea plugin 实践

arthas 插件下载地址 : plugins.jetbrains.com/plugin/1358… ,或者直接通过idea 进行下载。

功能列表

功能点非常的多,这里就不一一的去讲解,可以参考使用文档 ,不过最近一直在更新,使用文档中的命令名称可能有变化

4.1 安装 install

这个很简单的就是做了一个alias 然后jar -jar 启动arthas-boot.jar

curl -sk https://arthas.gitee.io/arthas-boot.jar -o ~/.arthas-boot.jar && echo "alias as.sh='java -jar ~/.arthas-boot.jar --repo-mirror aliyun --use-http'" >> ~/.bashrc && source ~/.bashrc

4.2 获取static 变量

和最初的一样,首先要获取classloader的hash 值,然后获取命令,这个是一个交互流程需要连贯性,后续只要是static 的通过static spring context的都需要有这个交互的流程,连贯的,都在同一个界面进行操作.粘贴执行,然后获取结果即可。

这里的classloader的hash 值缓存起来的

最后合并的脚本如下。

ognl -x 3 '@com.wangji92.arthas.plugin.demo.controller.StaticTest@INVOKE_STATIC_NAME' -c 316bc132

4.3 反射设置static field

通过反射进行设置static field ,参考:github.com/WangJi92/ar…

填写你想要修改的值、默认根据类型设置默认值 Str->"" Long -> 0L 等等。

ognl -x 3 '#field=@com.wangji92.arthas.plugin.demo.controller.StaticTest@class.getDeclaredField("INVOKE_STATIC_FINAL"),#modifiers=#field.getClass().getDeclaredField("modifiers"),#modifiers.setAccessible(true),#modifiers.setInt(#field,#field.getModifiers() & ~@java.lang.reflect.Modifier@FINAL),#field.setAccessible(true),#field.set(null,"设置的值")' -c 316bc132

4.4 spring context invoke

通过spring context 进行调用bean的方法、字段的内容。

Static Spring Context Invoke Method Field

首页要设置一下static spring context的路径

由于都是通过ognl 调用static的spring context 都需要classloader,这个就是配置的spring conetxt的地址信息:

@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context 参考demo 就需要配置这个地址。

ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getBean("commonController").getRandomInteger()' -c 316bc132 

Watch Spring Context Invoke Method Field

watch 这个是支持在spring mvc场景 通过watch 间接的获取spring context,需要出发一次接口的调用,可以参考 :github.com/WangJi92/ar…

watch -x 3 -n 1 org.springframework.web.servlet.DispatcherServlet **do**Dispatch '@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(params[0].getServletContext()).getBean("commonController").getRandomInteger()'

TimeTunnel Spring Context Invoke Method Field

这个是参考了横云断岭 的arthas 通过tt 获取spring context 为所欲为 ,可以参考这个文档:github.com/WangJi92/ar… 这里做了些什么?将整个连贯了起来,不需要记住参数信息,然后对于调用的参数进行简单的默认封装,复杂的参数场景不支持,需要手动拼接,参考上面的ognl 语法糖。

记录获取spring context

tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod

然后根据这个target 获取spring context 调用方法

tt -w 'target.getApplicationContext().getBean("commonController").getRandomInteger()' -x 3 -i 1000

4.5 获取某个spring 环境变量

获取spring 环境变量这里依托,static spring context ,当然这个watch 、和 tt 获取spring context的场景也是支持的。在线上环境、测试环境程序多复杂,你怎么知道环境中的变量一定是你配置的?

在nacos等等配置中心的场景,估计手速慢了一点点,可能就没有上去,这个时候就有这种需求获取当前的环境变量。

选中变量,然后右键执行命令。由于使用静态的static spring context 依然需要classloader的值.这里已经基本上是arthas的上层应用啦。

ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getEnvironment().getProperty("custom.name")' -c 316bc132

4.6 获取全部的spring 环境变量

比较优先级,最前面的一定优先级最高,你一定被spring的各种优先级顺序搞晕了,那么怎么办呢?

arthas idea plugin 支持获取当前的全部的环境变量,依次打印出来**,这样就可以了解优先级,**特别是接入了nacos、diamond等远程的配置中心,实现不一样肯定更晕了。参考文档:blog.csdn.net/xunjiushi97…

ognl -x 3 '#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#allProperties={},#standardServletEnvironment=#propertySourceIterator=#springContext.getEnvironment(),#propertySourceIterator=#standardServletEnvironment.getPropertySources().iterator(),#propertySourceIterator.{#key=#this.getName(),#allProperties.add(" "),#allProperties.add("------------------------- name:"+#key),#this.getSource() instanceof java.util.Map ?#this.getSource().entrySet().iterator.{#key=#this.key,#allProperties.add(#key+"="+#standardServletEnvironment.getProperty(#key))}:#{}},#allProperties' -c 316bc132

4.7 TimeTunnel Tt

方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测(可以重新触发,周期触发,唯一缺点对于ThreadLocal 信息丢失[隐含参数]、引用对象数据变更无效),这个方便二次触发,特别是自己调试不方便的情况下记录下来,二次触发、周期触发,不过自从段岭大神tt 为所欲为之后都被带偏了。

这里arthas 插件做了一些什么?增加了二次触发的一些常用的命令,不让使用者愁于记忆,整个过程更加的具有连贯性。

4.8 stack 堆栈

获取方法从哪里执行的调用栈(用途:源码学习调用堆栈,了解调用流程) 这个是非常好用的功能,对于喜欢乐于排查问题的小伙伴真是福音,arthas idea 插件只是修改的命令的集成,之前也处理自己编码过程中的问题,源码、问题排查技巧-Java Debug and Arthas: blog.csdn.net/u012881904/…

stack com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger -n 5

4.9 Decompile Class Jad

反编译方法、类的源码, 有时候需要查看提交的代码是否上线呢?这个功能就非常的友好。参考文档:github.com/WangJi92/ar…

javajad --source-only com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger

4.10 watch、trace

增加了默认参数、默认展开的层级 限制次数,使用者不用知道这些核心的参数,简单的使用就好了,要使用更加的高级的自己help 一下就知道了。

watch com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger '{params,returnObj,throwExp}' -n 5 -x 3

trace com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger -n 5

4.11 trace -E(层级的打印trace)

这个trace -E 是龙哥给我提的需求,确实有这样的需求,自己构造起来非常的麻烦,通过界面操作简化了一下,需要观察多个类、多个方法的场景。选择你需要的场景继续添加即可。

trace -E com.wangji92.arthas.plugin.demo.controller.CommonController|com.wangji92.arthas.plugin.demo.service.ArthasTestService traceE|**do**TraceE -n 5

4.12 Heap Dump

打印堆栈,有点类似 jmap -dump:format=b,file=/temp/dump.hprof pid 下载下来使用MAT分析即可。

heapdump /tmp/dump.hprof 打印堆栈信息

4.13 特殊用法链接

这个必须要说一下,这个特殊用法的链接在线上自己束手无措的时候可以查看一下,非常有用。

4.14 对于通过spring context 调用方法说明

通过spring context 调用复杂的方法其实是不支持的,由于这个操作起来不方便,还是必须手工处理一下。

比如这里的 Map<String, User> names 的处理方式可以借鉴一下子。

更多可以参考demo github.com/WangJi92/ar…

    /**
     * 复杂参数调用 场景
     * static spring context
     * ognl -x 3 '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),#springContext=@com.wangji92.arthas.plugin.demo.common.ApplicationContextProvider@context,#springContext.getBean("commonController").complexParameterCall(#{"wangji":#user})' -c e374b99
     *
     * watch get spring context 备注 需要调用一次方法
     * watch -x 3 -n 1  org.springframework.web.servlet.DispatcherServlet doDispatch '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(params[0].getServletContext()).getBean("commonController").complexParameterCall(#{"wangji":#user})'
     *
     * tt get spring context ,only first get time index ok
     * tt -w '#user=new com.wangji92.arthas.plugin.demo.controller.User(),#user.setName("wangji"),#user.setAge(27L),target.getApplicationContext().getBean("commonController").complexParameterCall(#{"wangji":#user})' -x 3 -i 1000
     * @return
     */
     
    @RequestMapping("complexParameterCall")
    @ResponseBody
    public String complexParameterCall(@RequestBody  Map<String, User> names) {
        if (names == null) {
            return "EMPTY";
        }
        return names.toString();
    } 

五、总结

本文简单的介绍了arthas idea plugin 诞生的前世今生的故事,解放了大家对于arthas 使用的一些记忆、机械性的重复工作,欢迎大家的使用,有需求可以联系我,尽量的满足。