作者 | 汪小哥
Arthas 对于很多 Java 开发者来说,已经不可分割了,在我们日常开发、线上问题排查中扮演了非常重要的角色。作为小开发的我,日常需要排查线上运营同学提的各种 bug、各种线上问题诊断、日常运维、线上问题优化等等。
在刚来公司时,我是比较恐惧运维任务的,代码不熟悉、各种问题比较多…几乎崩溃的状态,运维的一周基本上没有干活,完全是全身心投入到运维的任务中,排查问题效率低下。
由于深刻体验到了这种奔溃,我一直想改变这种状态,直到 Arthas 的开源,让我在这种崩溃的状态中减轻了不少负担,同时也让我成为了同事们咨询 Arthas 排查问题的小帮手~~ 虽然使用 Arthas 特别方便,但在此过程中也遇到一些问题,作为问题咨询小帮手也感到有点不方便,因此才造就了 Arthas idea 插件的诞生。
目前 Arthas 官方的工具还不够简单,需要记住一些命令,特别是一些扩展性特别强的高级语法,比如 ognl 获取 spring context 为所欲为,watch、trace 不够简单,需要构造一些命令工具的信息,因此只需要一个能够简单处理字符串信息的插件即可使用。
当在处理线上问题的时候需要最快速、最便捷的命令,因此 Idea Arthas plugin 插件还是有存在的意义和价值的。—这个是最初编写这个插件的最核心的理由。
Arthas IDEA plugin 实践
Arthas 的功能点非常的多(详见下方大图),这里就不一一的讲解了,可以参考使用文档 ,不过最近一直在更新,使用文档中的命令名称可能有变化。
插件安装
下载 arthas idea 插件:https://plugins.jetbrains.com/plugin/13581-arthas-idea
- Mac:
Preferences
->Plugins
- Windows:
Settings
->Plugins
Install Plugin form Disk… 导入插件
安装之后重启 IDEA 就可以愉快的使用啦!
获取 static 变量
首先要获取 classloader 的 hash 值,然后获取命令,这个是一个交互流程需要连贯性,后续只要是 static 的通过 static spring context 的都需要有这个交互的流程,连贯的,都在同一个界面进行操作.粘贴执行,然后获取结果即可。
这里的 classloader 的 hash 值缓存起来的
最后合并的脚本如下。
ognl -x 3 '@com.wangji92.arthas.plugin.demo.controller.StaticTest@INVOKE_STATIC_NAME' -c 316bc132
反射设置 static field
通过反射进行设置 static field ,参考:https://github.com/WangJi92/arthas-idea-plugin/issues/1
填写你想要修改的值、默认根据类型设置默认值 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
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,需要出发一次接口的调用,可以参考 :https://github.com/WangJi92/arthas-idea-plugin/issues/5
watch -x 3 -n 1 org.springframework.web.servlet.DispatcherServlet doDispatch '@org.springframework.web.context.support.WebApplicationContextUtils@getWebApplicationContext(params[0].getServletContext()).getBean("commonController").getRandomInteger()'
TimeTunnel Spring Context Invoke Method Field
这个是参考了横云断岭的 arthas 通过 tt 获取 spring context 为所欲为 ,可以参考这个文档:https://github.com/WangJi92/arthas-idea-plugin/issues/4 这里做了些什么?将整个连贯了起来,不需要记住参数信息,然后对于调用的参数进行简单的默认封装,复杂的参数场景不支持,需要手动拼接。
记录获取 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
获取某个 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
获取全部的 spring 环境变量
比较优先级,最前面的一定优先级最高,你一定被 spring 的各种优先级顺序搞晕了,那么怎么办呢?arthas idea plugin 支持获取当前的全部的环境变量,依次打印出来,这样就可以了解优先级,特别是接入了 nacos、diamond 等远程的配置中心,实现不一样肯定更晕了。
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
TimeTunnel Tt
方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测(可以重新触发,周期触发,唯一缺点对于 ThreadLocal 信息丢失[隐含参数]、引用对象数据变更无效),这个方便二次触发,特别是自己调试不方便的情况下记录下来,二次触发、周期触发,不过自从段岭大神 tt 为所欲为之后都被带偏了。这里 arthas 插件做了一些什么?增加了二次触发的一些常用的命令,不让使用者愁于记忆,整个过程更加的具有连贯性。
stack 堆栈
获取方法从哪里执行的调用栈(用途:源码学习调用堆栈,了解调用流程) 这个是非常好用的功能,对于喜欢乐于排查问题的小伙伴真是福音,arthas idea 插件只是修改的命令的集成,之前也处理自己编码过程中的问题,源码、问题排查技巧-Java Debug and Arthas:https://blog.csdn.net/u012881904/article/details/104591529
stack com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger -n 5
Decompile Class Jad
反编译方法、类的源码, 有时候需要查看提交的代码是否上线呢?这个功能就非常的友好。
jad --source-only com.wangji92.arthas.plugin.demo.controller.CommonController getRandomInteger
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
trace -E(层级的打印 trace)
trace -E 自己构造起来非常的麻烦,通过界面操作简化了一下,需要观察多个类、多个方法的场景。选择你需要的场景继续添加即可。
trace -E com.wangji92.arthas.plugin.demo.controller.CommonController|com.wangji92.arthas.plugin.demo.service.ArthasTestService traceE|doTraceE -n 5
Heap Dump
打印堆栈,有点类似 jmap -dump:format=b,file=/temp/dump.hprof pid 下载下来使用 MAT 分析即可。
heapdump /tmp/dump.hprof 打印堆栈信息
特殊用法链接
这个必须要说一下,这个特殊用法的链接在线上自己束手无措的时候可以查看一下,非常有用。
对于通过 spring context 调用方法说明
通过 spring context 调用复杂的方法其实是不支持的,由于这个操作起来不方便,还是必须手工处理一下。
比如这里的 Map names 的处理方式可以借鉴一下子。
更多可以参考 demo:https://github.com/WangJi92/arthas-plugin-demo
/**
* 复杂参数调用 场景
* 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 插件的安装与使用技巧,该插件解放了大家对于 Arthas 使用的一些记忆、机械性的重复工作,欢迎大家试用!