記錄生產一次linux負載高cpu使用率低的分析


前言

本文記錄下生產一次cpu使用率低,但是load average高的情況,如下兩圖,load average很高,但是cpu使用率很低

image-20201124145400481

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實戰筆記總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM