zoukankan      html  css  js  c++  java
  • Java 线上问题排查神器 Arthas 快速上手与原理浅谈

    【Arthas 官方社区正在举行征文活动,参加即有奖品拿哦~点击投稿

    作者 | 杨桢栋,笔名叫蛮三刀把刀,是一名一线互联网码农,留美访学一年,主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向。

    (1).png

    前言

    当你兴冲冲地开始运行自己的 Java 项目时,你是否遇到过如下问题:

    • 程序在稳定运行了,可是实现的功能点了没反应。
    • 为了修复 Bug 而上线的新版本,上线后发现 Bug 依然在,却想不通哪里有问题?
    • 想到可能出现问题的地方,却发现那里没打日志,没法在运行中看到问题,只能加了日志输出重新打包——部署——上线
    • 程序功能正常了,可是为啥响应时间这么慢,在哪里出现了问题?
    • 程序不但稳定运行,而且功能完美,但跑了几天或者几周过后,发现响应速度变慢了,是不是内存泄漏了?

    以前,你碰到这些问题,解决的办法大多是,修改代码,重新上线。但是在大公司里,上线的流程是非常繁琐的,如果为了多加一行日志而重新发布版本,无疑是非常折腾人的。

    现在,我们有了更为优雅的线上调试方法 - 来自阿里巴巴开源的 Arthas。

    下图是 Arthas 文档中对于为什么要使用它的描述,我进行了精简:

    1.png

    好了,前言已经超过字数了,哈哈,在本篇文章里,你能够了解:

    • Arthas 使用实例:帮助你快速让你上手,拯救你的低效率 Debug
    • 使用 Arthas 解决具体问题:看一下 Arthas 帮我拯救了多少时间
    • 相似工具:看看线上 Debug 还有没有别的工具可以使用
    • 原理浅谈:莫在浮沙筑高阁!你需要大概了解下 Arthas 的原理

    相信我,Arhas 绝对是你提升效率的利器,适合各种阶段的开发者,尤其适合我这种刚入门的新人(天天上班写 Bug 的人)。你不应该有这种东西是高阶程序员才应该去使用的思想,放心大胆的去用吧!

    线上 Debug 神器 Arthas

    Arthas 使用实例

    命令的详细文档请参考:alibaba.github.io/arthas/comm…

    快速启动

    快速启动它,你只需要两行命令:

    wget https://alibaba.github.io/arthas/arthas-boot.jar
    java -jar arthas-boot.jar
    

    2.png

    随后,在界面出现的进程中,选择你的程序序号,比如 1

    3.png

    这样你就进入了 arthas 的控制台。

    基本使用

    Arthas 有如下功能:

    4.png

    1. 首先是我认为的“上帝视角”指令:Dashboard

    当前系统的实时数据面板,按 ctrl+c 退出;
    当运行在 Ali-tomcat 时,会显示当前 tomcat 的实时信息,如 HTTP 请求的 qps, rt, 错误数, 线程池信息等等。

    通过这些,你可以对于整个程序进程有个直观的数据监控。

    5.png
    6.png

    2. 类加载问题相关指令

    7.png

    SC:查看 JVM 已加载的类信息

    通过 SC 我们可以看到我们这个类的详细信息,包括是从哪个 jar 包读取的,他是不是接口/枚举类等,甚至包括他是从哪个类加载器加载的。

    8.png

    上图中代码:

    [arthas@37]$ sc -d *MathGame
     class-info        demo.MathGame
     code-source       /home/scrapbook/tutorial/arthas-demo.jar
     name              demo.MathGame
     isInterface       false
     isAnnotation      false
     isEnum            false
     isAnonymousClass  false
     isArray           false
     isLocalClass      false
     isMemberClass     false
     isPrimitive       false
     isSynthetic       false
     simple-name       MathGame
     modifier          public
     annotation
     interfaces
     super-class       +-java.lang.Object
     class-loader      +-sun.misc.Launcher$AppClassLoader@70dea4e
                         +-sun.misc.Launcher$ExtClassLoader@69260973
     classLoaderHash   70dea4e
    

    SC 也可以查看已加载的类,帮助你看是否有没有纳入进来的类,尤其是在 Spring 中,可以判断的你的依赖有没有正确的进来。

    9.png

    上图中代码:

    # 查看JVM已加载的类信息
    [arthas@37]$ sc javax.servlet.Filter
    com.example.demo.arthas.AdminFilterConfig$AdminFilter
    javax.servlet.Filter
    org.apache.tomcat.websocket.server.WsFilter
    org.springframework.boot.web.filter.OrderedCharacterEncodingFilter
    org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter
    org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter
    org.springframework.boot.web.filter.OrderedRequestContextFilter
    org.springframework.web.filter.CharacterEncodingFilter
    org.springframework.web.filter.GenericFilterBean
    org.springframework.web.filter.HiddenHttpMethodFilter
    org.springframework.web.filter.HttpPutFormContentFilter
    org.springframework.web.filter.OncePerRequestFilter
    org.springframework.web.filter.RequestContextFilter
    org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
    Affect(row-cnt:14) cost in 11 ms.
     
    # 查看已加载类的方法信息
    [arthas@37]$ sm java.math.RoundingMode
    java.math.RoundingMode <init>(Ljava/lang/String;II)V
    java.math.RoundingMode values()[Ljava/math/RoundingMode;
    java.math.RoundingMode valueOf(I)Ljava/math/RoundingMode;
    java.math.RoundingMode valueOf(Ljava/lang/String;)Ljava/math/RoundingMode;
    Affect(row-cnt:4) cost in 6 ms.
    

    jad:反编译某个类,或者反编译某个类的某个方法。

    10.png

    上图中代码:

    # 反编译只显示源码
    jad --source-only com.Arthas
    # 反编译某个类的某个方法
    jad --source-only com.Arthas mysql
    [arthas@37]$ jad demo.MathGame
    ClassLoader:
    +-sun.misc.Launcher$AppClassLoader@70dea4e
      +-sun.misc.Launcher$ExtClassLoader@69260973
    Location:
    /home/scrapbook/tutorial/arthas-demo.jar
    /*
     * Decompiled with CFR.
     */
    package demo;
    import java.io.PrintStream;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.TimeUnit;
    public class MathGame {
        private static Random random = new Random();
        public int illegalArgumentCount = 0;
        public List<Integer> primeFactors(int number) {
            if (number < 2) {
                ++this.illegalArgumentCount;
                throw new IllegalArgumentException("number is: " + number + ", need >= 2");
            }
            ArrayList<Integer> result = new ArrayList<Integer>();
            int i = 2;
            while (i <= number) {
                if (number % i == 0) {
                    result.add(i);
                    number /= i;
                    i = 2;
                    continue;
                }
                ++i;
            }
            return result;
        }
        public static void main(String[] args) throws InterruptedException {
            MathGame game = new MathGame();
            do {
                game.run();
                TimeUnit.SECONDS.sleep(1L);
            } while (true);
        }
        public void run() throws InterruptedException {
            try {
                int number = random.nextInt() / 10000;
                List<Integer> primeFactors = this.primeFactors(number);
                MathGame.print(number, primeFactors);
            }
            catch (Exception e) {
                System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
            }
        }
        public static void print(int number, List<Integer> primeFactors) {
            StringBuffer sb = new StringBuffer(number + "=");
            for (int factor : primeFactors) {
                sb.append(factor).append('*');
            }
            if (sb.charAt(sb.length() - 1) == '*') {
                sb.deleteCharAt(sb.length() - 1);
            }
            System.out.println(sb);
        }
    }
    Affect(row-cnt:1) cost in 760 ms.
    

    3. 方法运行相关指令

    11.png

    watch:方法执行的数据观测

    你可以通过 watch 指令,来监控某个类,监控后,运行下你的功能,复现下场景,arthas 会提供给你具体的出参和入参,帮助你排查故障。

    12.png

    trace:输出方法调用路径,并输出耗时

    这个指令对于优化代码非常的有用,可以看出具体每个方法执行的时间,如果是 for 循环等重复语句,还能看出 n 次循环中的最大耗时,最小耗时,和平均耗时,完美!

    13.png

    tt:官方名为时空隧道

    这是我调试用的最多的指令,在你对某方法开启 tt 后,会记录下每一次的调用(你需要设置最大监控次数),然后你可以在任何时候会看这里面的调用,包括出参,入参,运行耗时,是否异常等。非常强大。

    14.png

    4. 线程调试相关指令

    15.png

    thread 相关命令:

    16.png

    thread -n:排列出 CPU 使用率 Top N 的线程。

    17.png

    thread -b:排查阻塞的线程

    我们代码有时候设计的不好,会引发死锁的问题,卡住整个线程执行,使用这个指令可以轻松的找到问题线程,以及问题的执行语句。

    18.png
    19.png

    5. 强大的 ognl 表达式

    众所周知,一般来说,表达式都是调试工具里最强的指令,哈哈。

    20.png

    在 Arthas 中你可以利用 ognl 表达式语言做很多事,比如执行某个方法,获取某个信息,甚至进行修改。

    21.png

    [arthas@19856]$ ognl '@com.Arthas@hashSet'
    @HashSet[
        @String[count1],
        @String[count2],
        @String[count29],
        @String[count28],
        @String[count0],
        @String[count27],
        @String[count5],
        @String[count26],
        @String[count6],
        @String[count25],
        @String[count3],
        @String[count24],
        
    [arthas@19856]$ ognl  '@com.Arthas@hashSet.add("test")'
    @Boolean[true]
    [arthas@19856]$
    # 查看添加的字符
    [arthas@19856]$ ognl  '@com.Arthas@hashSet' | grep test
        @String[test],
    [arthas@19856]$
    

    甚至你可以动态更换日志输出级别。

    22.png

    $ ognl '@com.lz.test@LOGGER.logger.privateConfig'
    @PrivateConfig[
        loggerConfig=@LoggerConfig[root],
        loggerConfigLevel=@Level[INFO],
        intLevel=@Integer[400],
    ]
    $ ognl '@com.lz.test@LOGGER.logger.setLevel(@org.apache.logging.log4j.Level@ERROR)'
    null
    $ ognl '@com.lz.test@LOGGER.logger.privateConfig'
    @PrivateConfig[
        loggerConfig=@LoggerConfig[root],
        loggerConfigLevel=@Level[ERROR],
        intLevel=@Integer[200],
      
    ]
    

    使用 Arthas 解决具体问题

    1. 响应时间异常问题

    工作中遇到一个优化问题,系统中一个导出表格的功能,响应时间长达 2 分钟,虽然给内部使用,但也不能这么夸张,用 trace 跟踪下方法,发现是其中的手机号加解密函数占用了非常大的时间,几千个手机号,进行了解密后加密的精彩操作,最终导致了两分钟的返回时间。

    23.png

    2. 某功能 Bug 导致服务器返回 500

    首先通过 trace 看异常报错的方法,之后通过 tt 排查方法,发现入参进来后,居然走错了方法(因为多态),走到了返回 null 的方法中,所以导致了 NPE 空指针错误。

    24.png
    25.png

    补充

    Arthas 还支持 Web Console,详见:alibaba.github.io/arthas/web-…

    26.png

    相似工具

    BTrace 一是个历史比较久的工具,观察下来 Arthas 其实和它的理念蛮相似的,相信 Arthas 也参考过 Btrace,作为一个学习样例来开发 Arthas。详细的优劣势看图:

    27.png

    其他的相似工具,还有 jvm-sandbox,有兴趣的朋友可以去看看。

    原理浅谈

    分为三个部分:

    • 启动
    • arthas 服务端代码分析
    • arthas 客户端代码分析

    28.png

    启动

    使用了阿里开源的组件 cli,对参数进行了解析:com.taobao.arthas.boot.Bootstrap

    29.png

    在传入参数中没有 pid,则会调用本地 jps 命令,列出 java 进程。

    30.png

    进入主逻辑,会在用户目录下建立 .arthas 目录,同时下载 arthas-core 和 arthas-agent 等 lib 文件,最后启动客户端和服务端。

    通过反射的方式来启动字符客户端。

    31.png

    服务端——前置准备

    看服务端启动命令可以知道 从 arthas-core.jar开始启动,arthas-core 的 pom.xml 文件里面指定了 mainClass 为 com.taobao.arthas.core.Arthas,使得程序启动的时候从该类的 main 方法开始运行。

    32.png

    • 首先解析入参,生成 com.taobao.arthas.core.config.Configure 类,包含了相关配置信息;
    • 使用 jdk-tools 里面的 VirtualMachine.loadAgent,其中第一个参数为 agent 路径, 第二个参数向 jar 包中的 agentmain() 方法传递参数(此处为 agent-core.jar 包路径和 config 序列化之后的字符串),加载 arthas-agent.jar 包;
    • 运行 arthas-agent.jar 包,指定了 Agent-Class为com.taobao.arthas.agent.AgentBootstrap。

    33.png

    上图中代码:

    public class Arthas {
        private Arthas(String[] args) throws Exception {
            attachAgent(parse(args));
        }
        private Configure parse(String[] args) {
            // 省略非关键代码,解析启动参数作为配置,并填充到configure对象里面
            return configure;
        }
        private void attachAgent(Configure configure) throws Exception {
               // 省略非关键代码,attach到目标进程
              virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
              virtualMachine.loadAgent(configure.getArthasAgent(),
                                configure.getArthasCore() + ";" + configure.toString());
        }
        public static void main(String[] args) {
                new Arthas(args);
        }
    }
    

    34.png

    服务端——监听客户端请求

    • 如果是 exit,logout,quit,jobs,fg,bg,kill 等直接执行;
    • 如果是其他的命令,则创建 Job,并运行;
    • 创建 Job 时,会根据具体客户端传递的命令,找到对应的 Command,并包装成 Process, Process 再被包装成 Job;
    • 运行 Job 时,反向先调用 Process,再找到对应的 Command,最终调用 Command 的 process 处理请求。

    服务端——Command 处理流程

    1. 不需要使用字节码增强的命令

    其中 JVM 相关的使用 java.lang.management 提供的管理接口,来查看具体的运行时数据。比较简单,就不介绍了。

    1. 需要使用字节码增强的命令

    字节码增加的命令统一继承 EnhancerCommand 类,process 方法里面调用 enhance 方法进行增强。调用 Enhancer 类 enhance 方法,该方法内部调用 inst.addTransformer 方法添加自定义的 ClassFileTransformer,这边是 Enhancer 类。

    Enhancer 类使用 AdviceWeaver(继承 ClassVisitor),用来修改类的字节码。重写了 visitMethod 方法,在该方法里面修改类指定的方法。visitMethod 方法里面使用了 AdviceAdapter(继承了 MethodVisitor类),在 onMethodEnter 方法, onMethodExit 方法中,把 Spy 类对应的方法(ON_BEFORE_METHOD, ON_RETURN_METHOD, ON_THROWS_METHOD 等)编织到目标类的方法对应的位置。

    在前面 Spy 初始化的时候可以看到,这几个方法其实指向的是 AdviceWeaver 类的 methodOnBegin, methodOnReturnEnd 等。在这些方法里面都会根据 adviceId 查找对应的 AdviceListener,并调用 AdviceListener 的对应的方法,比如 before,afterReturning, afterThrowing。

    客户端

    客户端代码在 arthas-client 模块里面,入口类是 com.taobao.arthas.client.TelnetConsole。

    主要使用 apache commons-net jar 进行 telnet 连接,关键的代码有下面几步:

    1. 构造 TelnetClient 对象,并初始化
    2. 构造 ConsoleReader 对象,并初始化
    3. 调用 IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), System.in, consoleReader.getOutput()) 处理各个流,一共有四个流:
      • telnet.getInputStream()
      • telnet.getOutputStream()
      • System.in
      • consoleReader.getOutput()

    请求时:从本地 System.in 读取,发送到 telnet.getOutputStream(),即发送给远程服务端。 响应时:从 telnet.getInputStream() 读取远程服务端发送过来的响应,并传递给 consoleReader.getOutput(),即在本地控制台输出。

    关于源码,深入下去还有很多东西需要生啃,我也没有消化得很好,大家可以继续阅读详细资料。

    总结

    Arthas 是一个**线上 **Debug 神器,小白也可以轻松上手。

    一键安装并启动 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

    参考文献

    开源地址:
    github.com/alibaba/art…
    官方文档:
    alibaba.github.io/arthas

    Arthas 征文活动火热进行中

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

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

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

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

  • 相关阅读:
    全站HTTPS底层实现原理
    python十个实战项目
    CP30,DBCP数据源配置
    FileUtils
    我的HttpClients工具
    Hibernate如何一个类映射两个表
    SSH2中实例化不了Action的一个原因
    二进制实现权限的分配管理
    myclips常用快捷键
    Hibernate 的*.hbm.xml文件的填写技巧
  • 原文地址:https://www.cnblogs.com/alisystemsoftware/p/13107988.html
Copyright © 2011-2022 走看看