zoukankan      html  css  js  c++  java
  • 中原银行 Arthas 实践之路

    作者 | 于爽 中原银行系统研发工程师,目前在技术平台室中间件小组从事分布式缓存、消息队列等相关工作。

    【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

    # target-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

    1.png

    /telnet访问:ip:3658

    2.png

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

    2.trace

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

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

    3.png

    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
    

    4.png

    三. 应用实践案例

    下面记录一些我行 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.
       * [1]: 11360 org.gradle.launcher.daemon.bootstrap.GradleDaemon
         [2]: 12196 com.durian.ddp.Application
       # 选择要附加的java进程编号
       2
       ...
    

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

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

    # 查看CPU使用率最高的5个线程
    thread -n 5
    at ***.TreeUtil.findMenuChildren(TreeUtil.java:94)
        at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
        at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
        at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
        at ***.TreeUtil.recursiveTree(TreeUtil.java:74)
        at ***.getOwnerDeparmentTree(DepartmentServiceImpl.java:550)
        ...
    at ***.TreeUtil.findMenuChildren(TreeUtil.java:94)
        at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
        at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
        at ***.TreeUtil.findMenuChildren(TreeUtil.java:92)
        at ***.TreeUtil.recursiveTree(TreeUtil.java:74)
        at ***.getOwnerDeparmentTree(DepartmentServiceImpl.java:550)
        ...
    ...
    

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

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

    # 5s为一个统计周期,统计TreeUtil中findMenuChildren方法的耗时
    monitor -c 5 ***.TreeUtil findMenuChildren
    

    5.png

    通过 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<ResourceTreeVo> 的 map,每个节点查找子节点列表的时候可以从 map 中获取。这样整个构建树的时间算法为 O(n)。

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

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

    6.png

    1. thread 命令查看线程信息

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

    # 查看线程情况
    thread 
    

    7.png

    8.png

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

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

    9.png

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

    2. stack 命令查看堆栈信息

    stack redis.clients.jedis.JedisSentinelPool$MasterListener
    

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

    10.png

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

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

    # 反编译RedisUtil类
    jad cn.com.zybank.testredis.starter.RedisUtil
    

    11.png

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

    12.png

    通过分析发现,每次使用 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 一样的国内优质项目能够开源。

    Arthas 征文活动火热进行中

    Arthas 官方正在举行征文活动,如果你有:

    • 使用 Arthas 排查过的问题
    • 对 Arthas 进行源码解读
    • 对 Arthas 提出建议
    • 不限,其它与 Arthas 有关的内容

    欢迎参加征文活动,还有奖品拿哦~点击投稿

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

  • 相关阅读:
    atcoder 2017Code festival C ——D题 Yet Another Palindrome Partitioning(思维+dp)
    51nod 1089最长回文子串V2 (manacher)
    Codeforces Round #362(Div1) D Legen...(AC自动机+矩阵快速幂)
    51nod 1532 带可选字符的多字符串匹配(位运算)
    51nod 1317 相似字符串对(容斥原理+思维)
    51nod 1526 分配笔名(字典树+贪心)
    51nod 1292 字符串中的最大值V2(后缀自动机)
    51nod 1277字符串中的最大值(拓展kmp)
    SPOJ:[DIVCNT3]Counting Divisors
    单纯形法模板
  • 原文地址:https://www.cnblogs.com/alisystemsoftware/p/13104314.html
Copyright © 2011-2022 走看看