原因總結
產生的原因一句話總結就是:等待磁碟I/O完成的程式過多,導致程式佇列長度過大,但是cpu執行的程式卻很少,這樣就體現到負載過大了,cpu使用率低。
下面內容是具體的原理分析:
在分析負載為什麼高之前先介紹下什麼是負載、多工作業系統、程式排程等相關概念。
什麼是負載
什麼是負載:負載就是cpu在一段時間內正在處理以及等待cpu處理的程式數之和的統計資訊,也就是cpu使用佇列的長度統計資訊,這個數字越小越好(如果超過CPU核心*0.7就是不正常)
負載分為兩大部分:CPU負載、IO負載
例如,假設有一個進行大規模科學計算的程式,雖然該程式不會頻繁地從磁碟輸入輸出,但是處理完成需要相當長的時間。因為該程式主要被用來做計算、邏輯判斷等處理,所以程式的處理速度主要依賴於cpu的計算速度。此類cpu負載的程式稱為“計算密集型程式”。
還有一類程式,主要從磁碟儲存的大量資料中搜尋找出任意檔案。這個搜尋程式的處理速度並不依賴於cpu,而是依賴於磁碟的讀取速度,也就是輸入輸出(input/output,I/O).磁碟越快,檢索花費的時間就越短。此類I/O負載的程式,稱為“I/O密集型程式”。
什麼是多工作業系統
Linux作業系統能夠同時處理幾個不同名稱的任務。但是同時執行多個任務的過程中,cpu和磁碟這些有限的硬體資源就需要被這些任務程式共享。即便很短的時間間隔內,需要一邊在這些任務之間進行切換到一邊進行處理,這就是多工。
執行中的任務較少的情況下,系統並不是等待此類切換動作的發生。但是當任務增加時,例如任務A正在CPU上執行計算,接下來如果任務B和C也想進行計算,那麼就需要等待CPU空閒。也就是說,即便是執行處理某任務,也要等到輪到他時才能執行,此類等待狀態就表現為程式執行延遲。
uptime輸出中包含“load average”的數字
[root@localhost ~]# uptime
11:16:38 up 2:06, 4 users, load average: 0.00, 0.02, 0.05
Load average從左邊起依次是過去1分鐘、5分鐘、15分鐘內,單位時間的等待任務數,也就是表示平均有多少任務正處於等待狀態。在load average較高的情況下,這就說明等待執行的任務較多,因此輪到該任務執行的等待時間就會出現較大的延遲,即反映了此時負載較高。
程式排程
什麼是程式排程:
程式排程也被一些人稱為cpu上下文切換意思是:CPU切換到另一個程式需要儲存當前程式的狀態並恢復另一個程式的狀態:當前執行任務轉為就緒(或者掛起、中斷)狀態,另一個被選定的就緒任務成為當前任務。程式排程包括儲存當前任務的執行環境,恢復將要執行任務的執行環境。
在linux核心中,每一個程式都存在一個名為“程式描述符”的管理表。該程式描述符會調整為按照優先順序降序排序,已按合理的順序執行程式(任務)。這個調整即為程式排程器的工作。
排程器劃分並管理程式的狀態,如:
- 等待分配cpu資源的狀態。
- 等待磁碟輸入輸出完畢的狀態。
下面在說一下程式的狀態區別:
下面舉例來說明程式狀態轉變:
這裡有三個程式A、B、C同時執行。首先,每個程式在生成後都是可執行狀態,也就是running狀態的開始,而不是現在執行狀態,由於在linux核心中無法區別正在執行的狀態和可執行的等待狀態,下面將可執行狀態和正在執行狀態都稱為running狀態。
- 程式A:running
- 程式B:running
- 程式C:running
running的三個程式立即成為排程物件。此時,假設排程器給程式A分配了CPU的執行許可權。
- 程式A:running (正在執行)
- 程式B:running
- 程式C:running
程式A分配了CPU,所以程式A開始處理。程式B和C則在此等待程式A遷出CPU。假設程式A進行若干計算之後,需要從磁碟讀取資料。那麼在A發出讀取磁碟資料的請求之後,到請求資料到達之前,將不進行任何工作。此狀態稱為“因等待I/O操作結束而被阻塞”。在I/O完成處理前,程式A就一直處於等待中,就會轉為不可中斷睡眠狀態(uninterruptible),並不使用CPU。於是排程器檢視程式B和程式C的優先順序計算結果,將CPU執行許可權交給優先順序較高的一方。這裡假設程式B的優先順序高於程式C。
- 程式A:uninterruptible (等待磁碟輸入輸出/不可中斷狀態)
- 程式B:running (正在執行)
- 程式C:running
程式B剛開始執行,就需要等待使用者的鍵盤輸入。於是B進入等待使用者鍵盤輸入狀態,同樣被阻塞。結果就變成了程式A和程式B都是等待輸出,執行程式C。這時程式A和程式B都是等待狀態,但是等待磁碟輸入輸出和等待鍵盤輸入為不同的狀態。等待鍵盤輸入是無限期的事件等待,而讀取磁碟則是必須短時間內完成的事件等待,這是兩種不同的等待狀態。各程式狀態如下所示:
程式A:uninterruptible (等待磁碟輸入輸出/不可中斷狀態)
程式B:interruptible (等待鍵盤輸入輸出/可中斷狀態)
程式C:running (正在執行)
這次假設程式C在執行的過程中,程式A請求的資料從磁碟到達了緩衝裝置。緊接著硬碟對核心發起中斷訊號,核心知道磁碟讀取完成,將程式A恢復為可執行狀態。
- 程式A:running (正在執行)
- 程式B:interruptible (等待鍵盤輸入輸出/可中斷狀態)
- 程式C:running (正在執行)
此後程式C也會變為某種等待狀態。如CPU的佔用時間超出了上限、任務結束、進入I/O等待。一旦滿足這些條件,排程器就可以完成從程式C到程式A的程式狀態切換。
負載的意義
負載表示的是“等待程式的平均數”。在上面的程式狀態變換過程中,除了running狀態,其他都是等待狀態,那麼其他狀態都會加入到負載等待程式中嗎?
事實證明,只有程式處於執行態(running)和不可中斷狀態(interruptible)才會被加入到負載等待程式中,也就是下面這兩種情況的程式才會表現為負載的值。
- 即便需要立即使用CPU,也還需等待其他程式用完CPU
- 即便需要繼續處理,也必須等待磁碟輸入輸出完成才能進行
下面描述一種直觀感受的場景說明為什麼只有執行態(running)和可中斷狀態(interruptible)才會被加入負載。
如:在很佔用CPU資源的處理中,例如在進行動畫編碼的過程中,雖然想進行其他相同型別的處理,結果系統反映卻變得很慢,還有從磁碟讀取大量資料時,系統的反映也同樣會變的很慢。但是另一方面,無論有多少等待鍵盤輸入輸出操作的程式,也不會讓系統響應變慢。
什麼場景會造成CPU低而負載確很高呢?
通過上面的具體分析負載的意義就很明顯了,負載總結為一句話就是:需要執行處理但又必須等待佇列前的程式處理完成的程式個數。具體來說,也就是如下兩種情況:
- 等待被授權予CPU執行許可權的程式
- 等待磁碟I/O完成的程式
cpu低而負載高也就是說等待磁碟I/O完成的程式過多,就會導致佇列長度過大,這樣就體現到負載過大了,但實際是此時cpu被分配去執行別的任務或空閒,具體場景有如下幾種。
場景一:磁碟讀寫請求過多就會導致大量I/O等待
上面說過,cpu的工作效率要高於磁碟,而程式在cpu上面執行需要訪問磁碟檔案,這個時候cpu會向核心發起呼叫檔案的請求,讓核心去磁碟取檔案,這個時候會切換到其他程式或者空閒,這個任務就會轉換為不可中斷睡眠狀態。當這種讀寫請求過多就會導致不可中斷睡眠狀態的程式過多,從而導致負載高,cpu低的情況。
場景二:MySQL中存在沒有索引的語句或存在死鎖等情況
我們都知道MySQL的資料是儲存在硬碟中,如果需要進行sql查詢,需要先把資料從磁碟載入到記憶體中。當在資料特別大的時候,如果執行的sql語句沒有索引,就會造成掃描表的行數過大導致I/O阻塞,或者是語句中存在死鎖,也會造成I/O阻塞,從而導致不可中斷睡眠程式過多,導致負載過大。
具體解決方法可以在MySQL中執行show full processlist命令檢視執行緒等待情況,把其中的語句拿出來進行優化。
場景三:外接硬碟故障,常見有掛了NFS,但是NFS server故障
比如我們的系統掛載了外接硬碟如NFS共享儲存,經常會有大量的讀寫請求去訪問NFS儲存的檔案,如果這個時候NFS Server故障,那麼就會導致程式讀寫請求一直獲取不到資源,從而程式一直是不可中斷狀態,造成負載很高。
結束語:大概內容就是這樣,如果有朋友遇到其他場景,歡迎留言補充。
作者:西門飛冰,一名90後it男,一直在北京工作,熱愛運動,熱愛冒險,熱愛旅行。
關注 民工哥技術之路 微信公眾號對話方塊回覆關鍵字:1024 可以獲取一份最新整理的技術乾貨。