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

邻近双11又开始另一轮的性能测试,陆续给大家奉上性能测试系列篇,从性能测试理论、性能测试案例高延迟(响应时间长)、CPU问题、内存问题、线上问题实战和性能测试书籍推荐等篇章。

上次篇《服务端性能测试指标及问题排查》,主要从理论的角度阐述性能测试指标和性能测试排除过程,从本篇开始讲高延迟的案例,主要从原因、问题分析、案例等进行分析和讲解。

- 1 - 

 概述 

CPU高是常见的性能问题,但CPU高并不一定都是有问题,有可能你的业务就是CPU密集型的业务。

如果CPU资源有限的情况下,本文可以提供一下优化CPU使用的方法,可以尽可能的优化,使CPU在可能的范围内降到最低。

- 2 - 

 造成CPU问题的原因 

代码Bug

    可能性太多…

意外的死循环

    手抖写的死循环或者计算失误导致死循环

| 线程数太多(线程数量是否比预期的异常的高)

    大量的线程切换:线程数太多可能会导致频繁的线程上下文的切换,浪费CPU资源。

    频繁创建销毁线程:线程的创建和销毁对系统的资源消耗比较大,如果一直在频繁的创建销毁临时线程导致资源占用,可能需要考虑下是否有其他更好的方案了。

    线程池不正常的使用:比如Cache线程池初始化比较少、最小和最大的数量相差比较大、业务并发突然增大可能导致同时去创建线程。

频繁的FGC

    导致频繁的FGC也会导致CPU高,比如由于JVM参数设置的不合理,年轻代和老年代的比例比例不合理导致频繁的FGC等。

不正常的使用某些类

    比如Map的初始大小给的太小,而后续的使用中存的东西太多,可能会频繁的resize。

    比如大数据量List的频繁查找,clone等。

    比如可重复利用资源的频繁初始化操作。

- 3 - 

 CPU问题的分析过程 

用到的工具和命令
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,这么多运行的是否正常,业务堆栈是否正常,该业务可能出现的问题等等。

- 4 - 

 案例分析 

案例介绍

测试时发现使用的的测试客户端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,之前遇到过类似案例。接着分析为什么这几个线程会卡到这。

根据代码分析都在执行这个操作的原因。

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消耗比较多。

以上是一个工作上简单案例的分析过程,实际工作中遇到的问题可能会复杂的多,过程可能会更曲折,需要从更多的方面去了解被测对象,甚至需要比开发自己更了解整个系统的架构,才能从多个方面去考虑问题,查找问题的真正原因

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值