美文网首页运维Bug追踪性能测试
服务端性能问题排查及优化---CPU高问题分析

服务端性能问题排查及优化---CPU高问题分析

作者: 土司阿哈 | 来源:发表于2019-02-14 08:28 被阅读148次

    CPU高是常见的性能问题,但CPU高并不一定都是有问题,有可能你的业务就是CPU密集型的业务。
    如果CPU资源有限的情况下,本文可以提供一下优化CPU使用的方法,可以尽可能的优化,使CPU在可能的范围内降到最低。

    可能造成CPU高的情况 :

    • 代码Bug
    • 意外的死循环
    • 线程数太多,频繁线程切换
    • 频繁的FGC
    • 不正常的使用某些类

    可能导致CPU高的情况

    1. 代码Bug
      可能性太多…
    2. 意外的死循环
      手抖写的死循环或者计算失误导致死循环
    3. 线程数太多(线程数量是否比预期的异常的高)
      大量的线程切换:线程数太多可能会导致频繁的线程上下文的切换,浪费CPU资源。
      频繁创建销毁线程:线程的创建和销毁对系统的资源消耗比较大,如果一直在频繁的创建销毁临时线程导致资源占用,可能需要考虑下是否有其他更好的方案了。
      线程池不正常的使用:比如Cache线程池初始化比较少、最小和最大的数量相差比较大、业务并发突然增大可能导致同时去创建线程。
    4. 频繁的FGC
      导致频繁的FGC也会导致CPU高,比如由于JVM参数设置的不合理,年轻代和老年代的比例比例不合理导致频繁的FGC等。
    5. 不正常的使用某些类
      比如Map的初始大小给的太小,而后续的使用中存的东西太多,可能会频繁的resize。
      比如大数据量List的频繁查找,clone等。
      比如可重复利用资源的频繁初始化操作。

    分析过程

    用到的工具和命令

    jvisualvm  
    jstack,jstat,jmap,ps,top
    
    

    CPU高的集中情况

    • 情况一:单核CPU使用率一直100%
      大多情况为死循环或者某个线程一直在执行大量运算操作

    • 情况二:单核CPU使用率不定时100%
      应用本身有周期性的任务
      非人为的周期性操作,比如FGC,大量数据的copy(list,map)

    • 情况三:每核CPU使用率都高
      需要结合堆栈、代码、资源占用、内存情况等,全面的考虑和分析。

    分析方法

    • 方法1
      Java Visualvm直接连接进程分析,能够比较直观的看到每个方法所用的CPU时间,更容易定位问题。大多数情况下生产环境无法使用。适用于被测对象问题能够复现,且配置的远程rmi监控的情况。生产环境不会配置远程监控。

      java服务启动时需配置启动参数:
      -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.143.136

      使用方法见以下演示。

    • 方法2
      在CPU高的时候抓线程堆栈,适用于CPU相对比较高的情况,如果条件允许可以把服务压力加大到尽量大,使CPU尽量高后去分析问题。

      抓取线程执行堆栈
      jstack pid > /tmp/jstack.txt

      找到占用cpu时间比较多的线程id
      ps -mp pid -o THREAD,tid,time | awk '{printf $2" "$8"\n" }' | sort

      把线程id转换为10进制,8进制?
      printf "%x\n" tid

      输出线程堆栈
      jstack pid | grep –A 20 tid

      连续抓取几次堆栈信息,分析正在运行的线程,分析当前操作是否耗费CPU,这么多运行的是否正常,业务堆栈是否正常,该业务可能出现的问题等等。

    案例分析

    案例介绍

    测试时发现使用的的测试客户端CPU高,表现为运行一段时间后,CPU突然100%,出现该情况后不能恢复正常。

    分析过程

    1. 首先抓取客户端的线程堆栈,看看能否发现什么可以的地方。
    2. 分析堆栈发现线程有1000多,大部分为BLOCKED状态,ACTIVE状态基本看到的都是nio的,暂时没看到问题。
    3. 继续搜索本地package的以下名称,看看有没有在执行自己代码的地方,正好发现一些类似以下的信息。
    Thread 1134: (state = IN_NATIVE)
     - java.net.NetworkInterface.getAll() @bci=0 (Compiled frame; information may be imprecise)
     - java.net.NetworkInterface.getNetworkInterfaces() @bci=0, line=334 (Compiled frame)
     - com.alibaba.rocketmq.remoting.common.RemotingUtil.getLocalAddress() @bci=0, line=112 (Compiled frame)
     - com.alibaba.rocketmq.client.ClientConfig.<init>() @bci=19, line=32 (Compiled frame)
     - com.alibaba.rocketmq.client.producer.DefaultMQProducer.<init>(java.lang.String, com.alibaba.rocketmq.remoting.RPCHook) @bci=1, line=95 (Compiled frame)
     - com.alibaba.rocketmq.client.producer.DefaultMQProducer.<init>(java.lang.String) @bci=3, line=86 (Compiled frame)
     - ********************MQProducer.<init>(java.lang.String, java.lang.String) @bci=71, line=62 (Compiled frame)
     - ********************.RocketMQ.sendMessage() @bci=76,line=119 (Compiled frame)     // 119为源代码行号
     - ********************.RocketMQ$1$1.safeRun() @bci=7, line=53 (Compiled frame)
     - ********************.SafeRunnable.run() @bci=1, line=13 (Compiled frame)
     - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=95, line=1145 (Compiled frame)
     - java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=5, line=615 (Interpreted frame)
     - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)
    

    发现这些线程都在执行java.net.NetworkInterface.getAll() ,此方法比较耗费CPU,之前遇到过类似案例。接着分析为什么这几个线程会卡到这。

    1. 根据代码分析都在执行这个操作的原因。
    public void sendMessage() {
        try {
            // 略…
            Message msg = new Message("Performace", msgContent.getBytes("UTF-8"));
            if (producer == null) {
                producer = new MQProducer("Performace", "192.168.143.135:9876");      (1)
                producer.start();
                rst = producer.product(msg);
            } else {
                rst = producer.product(msg);
            }
            // 略…
        } catch (Exception e) {
            if (producer != null) {
                producer.shutdown();
                producer = null;
            }
        }
    }
    

    sendMessage方法会被随机的注册到一个timer线程池上,有可能会在同一时间点或者很近时间点同时执行该方法。
    producer.product(msg);为给远端发送信息,如果因为网络原因或者其他未知原因导致Exception,会把producer赋值为null,当再次执行sendMessage会重新初始化producer,如果恰好有多线程并发执行sendMessage,可能会导致重复初始化以及其他并发问题,导致恶性循环,恰好这个过程对CPU消耗比较多。

    最后

    以上是一个工作上简单案例的分析过程,实际工作中遇到的问题可能会复杂的多,过程可能会更曲折,需要从更多的方面去了解被测对象,甚至需要比开发自己更了解整个系统的架构,才能从多个方面去考虑问题,查找问题的真正原因。
    本文由郭军英提供
    2019年连续五十三天修心 土司于北京

    相关文章

      网友评论

        本文标题:服务端性能问题排查及优化---CPU高问题分析

        本文链接:https://www.haomeiwen.com/subject/lrukeqtx.html