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 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

  • 相关阅读:
    streamsets 集成 cratedb 测试
    streamsets k8s 部署试用
    streamsets rest api 转换 graphql
    StreamSets sdc rpc 测试
    StreamSets 相关文章
    StreamSets 多线程 Pipelines
    StreamSets SDC RPC Pipelines说明
    StreamSets 管理 SDC Edge上的pipeline
    StreamSets 部署 Pipelines 到 SDC Edge
    StreamSets 设计Edge pipeline
  • 原文地址:https://www.cnblogs.com/alisystemsoftware/p/13107988.html
Copyright © 2011-2022 走看看