中原银行 Arthas 实践之路

Arthas 是一款 Java应用开源诊断工具,由于其强大的问题排查及诊断能力,自其开源以来广受开发者的关注和使用,多次登顶 GitHub Trending,并得到国内多家技术媒体的推荐分享。

一. 定制化功能改造

Arthas 可以通过简单的命令交互模式,接入运行的 JVM,快速定位和诊断线上程序运行问题。在不重启服务的情况下,实时、动态的修改相关 code,并实时生效。具体工作原理如下:

  1. 连接JVM:通过attach机制,通过attach pid连接正在运行的JVM;
  2. 查看及修改JVM字节码:通过instrument技术对运行中的JVM附加或修改字节码来实现增强的逻辑。

2018 年底,中原银行开始投入人员对 Arthas 进行调研,在开源社区了解了主要功能,并通过阅读 Arthas 工程大纲,明晰整体工程结构,整个执行过程如下:Arthas 底层调用 rt.jar 包的 ManagementFactory 获取整个 jvm 内部信息,通过命令集成与后端交互,执行,返回结果,整个工程简单清晰,容易上手。

2019 年初,中原银行技术团队开始使用推广 Arthas 定位和诊断线上问题。

出于保护客户敏感信息的严格要求,同时切实保障生产环境业务系统的稳定运行,我们对 Arthas 的部分功能进行了定制化改造,对一些命令进行了隐藏:

  1. watch:watch方法可以在没有打印日志的情况下,看到方法的入参和返回值,有可能暴露客户的敏感信息;
  2. mc、redefine:mc组合rdefine可以对代码进行热更新,不能满足我行生产运行管理规范要求。

同时,出于使用需要,定制化开发了 gc 等命令:

  1. gc:实时动态展示年轻代,年老代垃圾百分比,回收次数及耗时等情况。

下一步,我行计划在全部开发测试环境、部分生产环境推广使用 Arthas 来进行问题排查与定位诊断。同时采用内部技术分享的形式向行内应用开发团队普及推广 Arthas 的使用。

二. 重点使用功能

除了日常问题排查使用到的方法外,Arthas 还有一些强大的功能,深受中原银行技术团队喜爱。

1.target-ip

arget-ip 为指定绑定的IP,如果不指定IP,Arthas只listen 127.0.0.1,所以如果想从远程连接,则可以使用 --target-ip参数指定listen的IP,java -jar arthas-boot.jar --target-ip IP

绑定远程访问IP后,可以在通过telnet或http的方式远程连接 Arthas 进行问题排查。

web端访问地址:ip:8563

中原银行 Arthas 实践之路

/telnet访问:ip:3658

中原银行 Arthas 实践之路

当线上应用出现问题时,可以将问题机器隔离起来,通过Arthas在启动时指定target-ip,多方技术人员可同时通过远程连接进行问题排查。

2.trace

查看方法内部调用路径,并输出方法路径上的每个节点上耗时

trace ClassName methodName

使用 trace 命令可以一层一层追踪耗时在哪里 ,在进行性能调优的时候十分有效。

中原银行 Arthas 实践之路

3.ognl

ognl 是应用于 Java 中的一个开源的表达式语言,作用是对数据进行访问,它拥有类型转换、访问对象方法、操作集合对象等功能,通过 ognl 可以完成一些列强大的操作。

  • 执行静态方法

使用ognl调用静态方法

ognl “@类名@方法名(参数)”

  • 获取静态属性

使用ognl获取静态属性

ognl “@类名@属性名”

  • 示例:修改日志等级

查找当前类的classloader hashcode

sc -d 类名 | grep classLoaderHash

用OGNL获取logger

ognl -c * '@类名@logger'

单独设置该类的logger level

ognl -c * '@类名@logger.setLevel(@ch.qos.logback.classic.Level@DEBUG)'

全局设置logger level

ognl -c * '@org.slf4j.LoggerFactory@getLogger("root").setLevel(@ch.qos.logback.classic.Level@DEBUG)'

4.gc

gc 是我行定制化开发的功能,源自于 jstat -gcutil pid timeinterval 命令,其中 pid 可以从 Arthas 中获取,timeinterval(单位为毫秒)表示 gc 每次时间间隔,默认为 1s。

查看应用gc情况(timeinterval表示间隔时间,单位毫秒,默认为1S)

gc -i timeinterval -n 5

中原银行 Arthas 实践之路

三. 应用实践案例

下面记录一些我行 Arthas 应用实践案例(由于行内代码保密性要求,下文所示案例均为场景复现所写示例代码)

案例一:系统 CPU 使用率高

问题描述:业务人员反馈后台管理系统其中一个页面响应时间很长,登录服务器上发现 CPU 使用率较高,达到 80% 左右。

1. 启动 Arthas,附加到对应的 java 进程

注意:Arthas 启动时要使用与 Java 进程相同的启动用户。

启动Arthas

java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.2.0
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.

  • #选择要附加的java进程编号
    2

...

2. thread 命令查看 CPU 使用率高的线程

启动 Arthas,附加到对应的 java 进程,执行 thread -n 5 查看 CPU 使用率最高的 5 个线程的堆栈。

中原银行 Arthas 实践之路

3. 通过 monitor 命令查看方法的调用次数与耗时

通过 thread 命令已经定位到 CPU 主要消耗在 TreeUtil 的 findMenuChildren 方法上,通过 monitor 命令查看方法的具体调用次数与耗时。

5s为一个统计周期,统计TreeUtil中findMenuChildren方法的耗时

monitor -c 5 *.TreeUtil findMenuChildren

中原银行 Arthas 实践之路

通过 monitor 命令可以明确该方法单次调用平均耗时为 17 ~ 20ms,但是调用次数多,所以整体上页面响应慢。

4. 通过 jad 命令反编译 TreeUtil 类,查看源码

[arthas@12196]$ jad com.durian.ddp.utils.TreeUtil
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@18b4aac2
+-sun.misc.Launcher$ExtClassLoader@244038d0
Location:
/*

  • Decompiled with CFR.
    *
  • Could not load the following classes:
  • *.ResourceTreeVo
    */

...
public class TreeUtil {

public static ResourceTreeVo findMenuChildren(ResourceTreeVo resourceTreeVo, List<ResourceTreeVo> treeNodes) {
    for (ResourceTreeVo resource : treeNodes) {
        if (!resourceTreeVo.getResourceId().equals(resource.getResourceParentId())) continue;
        if (resourceTreeVo.getChildResourceVo() == null) {
            resourceTreeVo.setChildResourceVo(new ArrayList());
        }
        resourceTreeVo.getChildResourceVo().add(TreeUtil.findMenuChildren(resource, treeNodes));
    }
    return resourceTreeVo;
}
public static List<ResourceTreeVo> recursiveTree(List<ResourceTreeVo> list) {
    ArrayList<ResourceTreeVo> trees = new ArrayList<ResourceTreeVo>();
    for (ResourceTreeVo treeNode : list) {
        if (!StringUtils.isEmpty(treeNode.getResourceParentId())) continue;
        trees.add(TreeUtil.findMenuChildren(treeNode, list));
    }
    return trees;
}

}

通过 jad 命令查看源码可以发现,此处的业务逻辑大致是通过 ResourceTreeVo 对象的 resourceParentId 字段把一个列表构建一个树。在 findMenuChildren 方法中存在递归调用,而且每一次调用都需要遍历整个 ResourceTreeVo 列表来查找子节点,时间复杂度为 O(n)。所以在 ResourceTreeVo 列表元素比较多的时候,会很耗时。

5. 解决问题

定位到问题就方便解决了,可以通过提前基于 list 构建一个 parentId->List 的 map,每个节点查找子节点列表的时候可以从 map 中获取。这样整个构建树的时间算法为 O(n)。

案例二:应用线程连接数异常

问题描述:服务器句柄数耗尽,查看发现某个应用占用句柄数较多。

中原银行 Arthas 实践之路

1. thread 命令查看线程信息

启动 Arthas,附加到对应的 java 进程,执行 thread 查看线程情况。

查看线程情况

thread

中原银行 Arthas 实践之路
中原银行 Arthas 实践之路

看到有大量的 MasterListener-mymaster-* 线程处于连接状态,一直没有释放。

选择其中一个线程查看堆栈信息

thread id

中原银行 Arthas 实践之路

发现这些线程是由 redis.clients.JedisSentinelPool$MasterListener 产生的,那么接下来就来查看一下 JedisSentinelPool$MasterListener 的调用情况。

2. stack 命令查看堆栈信息

stack redis.clients.jedis.JedisSentinelPool$MasterListener

触发一次应用请求,打印出如下堆栈信息:

中原银行 Arthas 实践之路

通过调用链定位到 RedisUtil 类,发现每次请求否会触发 RedisUtil.getJedis 方法调用 JedisSentinelPool$MasterListener,那么下一步我们反编译一下 REedisUtil 类。

3. jad 命令反编译查看代码

反编译RedisUtil类

jad cn.com.zybank.testredis.starter.RedisUtil

中原银行 Arthas 实践之路

查看 getJedis 方法,发现 getJedis 每调用一次都会新建一个 JedisSentinelPool 。

中原银行 Arthas 实践之路

通过分析发现,每次使用 redis 时,都会调用 getJedis 方法创建一个新的 JedisSentinelPool,从而启动一个 MasterListener-mymaster-* 线程,由于该线程会一直保持监听,不会自动释放,故随着应用请求的增加线程数一直增加从而导致连接数占满。

4. 解决问题

针对该问题,只需创建一个全局的 JedisSentinelPool,每次获取 redis 连接时都从该连接池获取即可,这里不再对代码进行展示。

四.总结建议

我行在使用 Arthas 以前,线上问题排查往往需要查网络、jps、jstack、jmap、jhat、jstat、hprof 等一系列操作,费时费力。目前,大多数的常见问题都可以使用 Arthas 轻松定位,迅速解决。

一键安装并启动 Arthas

  • 方式一:通过 Cloud Toolkit 实现 Arthas 一键远程诊断

Cloud Toolkit 是阿里云发布的免费本地 IDE 插件,帮助开发者更高效地开发、测试、诊断并部署应用。通过插件,可以将本地应用一键部署到任意服务器,甚至云端(ECS、EDAS、ACK、ACR 和 小程序云等);并且还内置了 Arthas 诊断、Dubbo工具、Terminal 终端、文件上传、函数计算 和 MySQL 执行器等工具。不仅仅有 IntelliJ IDEA 主流版本,还有 Eclipse、Pycharm、Maven 等其他版本。

推荐使用 IDEA 插件下载 Cloud Toolkit 来使用 Arthas:http://t.tb.cn/2A5CbHWveOXzI7sFakaCw8

  • 方式二:直接下载

地址:https://github.com/alibaba/arthas

随着我行全面深化使用 Arthas,也发现了一些有待改进提升的功能,希望进一步优化完善。

在进行 trace 的时候,只要调用链中有异步,堆栈就会断掉,无法 trace 到子线程内部,只能手动逐层跟进 trace,效率较低。
希望 tt 命令能够添加异步开关,如果开关开启, 那么 COST 即可显示异步得到结果的耗时。
截至目前,Arthas GitHub Star 已经突破 2.4 万了,希望 Arthas 能够获得更多全球开发者的关注和喜爱,也期待更多像 Arthas 一样的国内优质项目能够开源。

本文转自<阿里巴巴云原生技术圈>——阿里巴巴云原生小助手

上一篇:Arrays.asList() 需要注意


下一篇:利用 Arthas 精准定位 Java 应用 CPU 负载过高问题