记录生产一次linux负载高cpu使用率低的分析

前言

本文记录下生产一次cpu使用率低,但是load average高的情况,如下两图,load average很高,但是cpu使用率很低

image-20201124145400481

这个笔记和 java线程生命周期 CPU和磁盘IO实战笔记总结 是一块的。建议先看 java线程生命周期 -> 记录生产一次linux负载高cpu使用率低的分析 -> CPU和磁盘IO实战笔记总结

目录

前言

load average定义

平均负载与CPU使用率关系

case1:CPU密集型java应用

case2:IO密集型java应用

case2.1.磁盘IO密集型应用

case2.2.网络IO密集型应用

case3:线程上下文大量切换也会导致cpu使用率增高,平均负载也变高

总结


image-20201124145424817

先说下这个机器配置:16C16G,用于jenkins构建服务器

通常指的load average是和cpu有关,cpu越高,load average越高,但是本次问题情况正好相反,问题出现在哪里? 是不是对平均负载理解的不正确呢?

 

load average定义

平均负载是指单位时间内,系统处于可运行状态不可中断状态的平均进程数,也就是平均活跃进程数,它和CPU使用率并没有直接关系。

可运行状态的进程,是指正在使用CPU或者正在等待CPU的进程,也就是我们常用ps命令看到的,处于R状态(Running 或 Runnable)的进程。

不可中断状态的进程则是正处于内核态关键流程中的进程,并且这些流程是不可打断的,比如最常见的是等待硬件设备的I/O响应。

比如,当一个进程向磁盘读写数据时,为了保证数据的一致性,在得到磁盘回复前,它是不能被其他进程或者中断打断的,这个时候的进程就处于不可中断状态。如果此时的进程被打断了,就容易出现磁盘数据与进程数据不一致的问题。

因此前面自己的理解是错误的,比如1min内的平均负载load average指的是1min内正在使用CPU进程+正在等待CPU的进程+等待IO的进程,同理5min、15min也是如此计算。

但是,我们平时是java应用,一个应用开启多个线程,这个就涉及到线程和进程的关系了。

linux内进程就是我们ps -fe 看到的,线程是属于进程的,一个进程至少有一个线程,对于我们java应用来说,一个java进程通常有多个线程。线程又称为Light—Weight Process,cpu处理线程是使用分片法,线程也涉及到使用cpu资源和IO(线程向磁盘读写数据),因此平均负载也简单定义为1min内正在使用CPU线程+正在等待CPU的线程+等待IO的线程

 

平均负载与CPU使用率关系

case1:对于CPU密集型java应用,并发高的情况下,cpu使用率飚高,此时load average也高,此时平均负载与CPU使用率是一致的。

case2:对于IO密集型java应用,由于等待IO响应的线程数增加,导致平均负载高,但是CPU使用率不一定高。

case3:线程上下文大量切换也会导致平均负载升高,此时的CPU使用率也会比较高。

下面对这三种情况以实际例子说明:

 

case1:CPU密集型java应用

对于case1,比如写个使用fastjson循环解析json字符串的应用,使用jmeter压测,cpu很容易飚高,查看到load average也变高。

比如下面这个代码,直接就把cpu打满了,在2C2G服务器上使用jmeter并发线程2,测试,load average在1,5min内是4.15, 2.29。

 /**
      * 使用正则表达式让cpu达到100%,这个结果计算要15s
      */
     @Override
     public void cpuRegex() {
         String regex = "(\\w+,?)+";
         String val = "abcdefghijklmno,abcdefghijklmno+";
         log.info("正则结果->{}", val.matches(regex));//val.matches耗费cpu
     }

case2:IO密集型java应用

case2.1.磁盘IO密集型应用

对于case2,写个频繁写入和读取磁盘数据的应用,使用jmeter压测,使用iostat检测磁盘,查看到load average也变高。

image-20201202213716163

测试代码如下

 @Override
     public void io() {
         try {
             ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
             URL url = (classLoader != null ?
                     classLoader.getResource("test-service.2020-01-09.log") :
                     ClassLoader.getSystemResource("test-service.2020-01-09.log"));
             String logText = IOUtils.toString(url, Charset.forName("utf8"));//读取resources下的test-service.2020-01-09.log文件内
             
             File targetFile = null;
             String os = System.getProperty("os.name");
             if (StrUtil.containsIgnoreCase(os, "linux")) {
                 targetFile = new File("/data/log/allocate/test-service-"+number.getAndIncrement()+".log");
             } else {
                 targetFile = new File("D:\\data\\allocate\\test-service-"+number.getAndIncrement()+".log");
             }
             boolean exists = targetFile.exists();
             if (!exists) {
                 targetFile.createNewFile();
             }
             FileOutputStream out = new FileOutputStream(targetFile);
             out.write(logText.getBytes("utf8"));//阻塞io操作,每次请求都写文件,而且是同步写的方式,这样磁盘IO就很大了
             out.close();
             
         }
         catch (IOException ex) {
             throw new IllegalArgumentException("Unable to load factories from location [" +
                     "test-service.2020-01-09.log" + "]", ex);
         }
     }

case2.2.网络IO密集型应用

上面是磁盘IO阻塞,那么如果是网络IO阻塞,是否会对平均负载造成影响呢?测试如下

测试代码

 @Override
     public void socketIO() {
         Socket socket = new Socket();
         try {
             socket.setSoTimeout(60000);//设置读取超时时间为60s
             socket.setTcpNoDelay(true);
             socket.connect(new InetSocketAddress("xxx.xxx.xxx.xxx", 80));//连接另外一个nginx服务器的80端口
             InputStream in = socket.getInputStream();
             byte[] b = new byte[1024];
             int read = in.read(b);//阻塞在读上
         } catch (IOException e) {
             log.error("读取超时,异常{}", e.getMessage());
         } finally {
             try {
                 socket.close();
             } catch (IOException e) {
                 log.error("socket关闭异常", e.getMessage());
             }
         }
     }

在使用jmeter压测,发现此时平均负载很低,cpu也很低,说明网络阻塞并不会影响平均负载。

 

case3:线程上下文大量切换也会导致cpu使用率增高,平均负载也变高

比如下面这样代码,一次请求内线程阻塞了2次,释放了2次cpu资源使用,这样在并发下就会导致上下文切换频繁,线程上下文频繁切换可通过vmstat查看cs指标,这个在我并发200的测试条件下,线程上下文切换为3w多。

 @Override
     public void cpuSwitch() {
         try {
             //业务代码
             Thread.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         int i = 1+1;
         //业务代码
         Thread.yield();
         int j = 1+1;
     }

 

总结

平均负载可以基本认为=正在使用CPU+者正在等待CPU的进程+磁盘IO,对于一个4C8G的服务器,如果负载低于2.8,说明正常,超过的不多,先看cpu,再看io,通常cpu100%虽然平均负载高,但是不会高的很离谱,高的离谱了,比如自己测试的磁盘IO例子(同步向文件写数据),那么基本说明是磁盘IO问题。cpu高,可以通过jstack命令或show-busy-java-threads脚本来定位,io高只能通过iostat、iotop来定位。

总结下磁盘IO高的几个原因:

1.频繁的向磁盘写入或读取大量数据

2.磁盘坏了,磁盘性能不好(碎片太多,nfs磁盘)

对于我们生产这次平均负载增高,原因是虚机所在的母机上的一块磁盘坏了导致平均负载增高。

 

具体的cpu和io实战分析,请看另外的笔记 CPU和磁盘IO实战笔记总结

 

 

 

 

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页