當前位置: 妍妍網 > 碼農

糟糕,CPU 100% 了!!!

2024-03-03碼農

前言

cpu使用率100% 問題,是一個讓人非常頭疼的問題。因為出現這類問題的原因千奇百怪,最關鍵的是它不是必現的,有可能是系統執行了一段時間之後,在突然的某個時間點出現問題。

今天特地把我和同事,之前遇到過的cpu使用率100%的問題,總結了一下,給有需要的朋友一個參數。

1 一次性獲取的數據太多

我之前參與過餐飲相關的業務系統開發,當時我所在的團隊是菜品的下遊業務。

當時菜品系統有菜品的更新,會發 kafka 訊息,我們系統訂閱該 topic ,就能獲取到最近更新的菜品數據。

同步菜品數據的功能,上線了一年多的時候,沒有出現過什麽問題。

但在某一天下午,我們收到了大量CPU100%的報警信件。

追查原因之後發現,菜品系統出現了bug,我們每次獲取到的都是全量的菜品數據,並非增量的數據。

一次性獲取的數據太多。

菜品修改還是比較頻繁的,也就是說我們系統,會頻繁的讀取和解析大量的數據,導致CPU不斷飆升。

其根本原因是頻繁的 full gc

2 kafka自動確認

之前我們的餐飲子系統中間,是透過訊息中介軟體: kafka 進行通訊的。

上遊系統中產生了數據,寫入db之後,然後把相關業務單據的id,透過kafka訊息發送到broker上。

下遊系統訂閱相關topic的訊息,獲取業務單據的id,然後呼叫上遊系統的業務查詢介面,獲取相關業務數據。

剛開始為了方便,我們消費訂單訊息時,kafka的確認機制,使用的是 自動確認 (可以少寫點程式碼)。

剛開始問題不大。

隨著業務的發展,使用者量越來越多,每天產生的kafka訊息也越來越多。

終於開始爆出了cpu使用率100%的問題。

後來,我們把kafka的consumer,消費訊息後改成手動確認,cpu使用率100%的問題就被解決了。

3 死迴圈

在實際工作中,可能每個開發都寫過 死迴圈 的程式碼。

死迴圈有兩種:

  1. 在while、for、forEach迴圈中的死迴圈。

  2. 無限遞迴。

這兩種情況,程式會不停的執行,使用 寄存器 保存 迴圈次數 或者 遞迴深度 ,一直占用cpu,導致cpu使用率飆升。

在使用JDK1.7時,還有些死迴圈比如多執行緒的環境下,往HashMap中put數據,可能會導致 連結串列 出現 死迴圈

就會導致cpu不斷飆高。

4 多執行緒導數據

之前我們組有位同事做了一個供應商excel數據匯入功能。

該功能上線之後發現excel中數據只要稍微多一點,匯入的耗時時間就會很長。

因為匯入供應商相關的業務邏輯有些復雜,涉及了多張表,而且是單執行緒中一條條按順序匯入的。

那位同事為了提升匯入數據的效能,將 單執行緒 匯入,改成了使用執行緒池的 多執行緒 匯入。

這樣改造之後,excel數據匯入的速度確實提升了很多。

但上線之後,卻帶來另外一個問題,即:CPU使用率一路飆升。

多執行緒匯入數據,如果執行緒數量比較多,會存在大量執行緒 上下文切換 的過程,這個過程非常消耗CPU資源。

5 同步大量檔

我之前參與過遊戲平台的開發。

遊戲廠商的遊戲接入我們平台,我們幫他們推廣,賺了錢進行分成。

每一款遊戲都有一個客製化的官網,網域名稱、圖片和樣式都不一樣。

當時出於效能考慮,我們當時使用了 FreeMarker 樣版引擎,為每一款遊戲都生成專門的 html 的靜態官網。

當時提供了十幾個不同的樣版,可以給遊戲的營運同學選擇。

原本是沒啥問題的。

但有一次節日活動,為了增加一些喜慶的元素,在每一個樣版檔中都加了一些樣式。

這就需要把所有遊戲的官網,用新的樣版重新生成一次了。

生成完畢之後,需要把所有的html檔,一次性同步到web伺服器的指定目錄下。

由於涉及到了大量檔的同步,導致存放檔的那台套用伺服器CPU飆升的很高。

6 死結

為了防止並行場景中,多個執行緒修改公共資源,導致的數據異常問題。

很多時候我們會在程式碼中使用 synchronized 或者 Lock 加鎖。

這樣多個執行緒進入臨界方法或者程式碼段時,需要競爭某個物件或者類的鎖,只有搶到相應的鎖,才能存取臨界資源。其他的執行緒,則需要等待,擁有鎖的執行緒釋放鎖,下一次可以繼續競爭那把鎖。

有些業務場景中,某段程式碼需要執行緒獲取多把鎖,才能完成業務邏輯。

但由於程式碼的bug,或者釋放鎖的順序不正確,可能會引起 死結 的問題。

例如:

"pool-4-thread-1" prio=10 tid=0x00007f27bc11a000 nid=0x2ae9 waiting on condition [0x00007f2768ef9000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000090e1d048> (a java.util.concurrent.locks.ReentrantLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)

比如執行緒a擁有鎖c,需要獲取鎖d,才能完成業務邏輯。

而剛好此時執行緒b擁有鎖d,需要獲取鎖c,也能完成業務邏輯。

執行緒a等待執行緒b釋放鎖,而執行緒b等待執行緒a釋放鎖,兩個執行緒都持有對方需要的鎖,無法主動釋放,就會出現死結問題。

死結會導致CPU使用率飆升。

7 正則匹配

不知道你使用過正規表式沒有?

有時候我們為了驗證使用者輸入的手機號、信箱、身份證號、網頁地址是否合法。

通常情況下,會使用正規表式,例如:

^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~/])+$

這個正規表式可以分為三個部份:

  • 第一部份匹配 http 和 https 協定。

  • 第二部份匹配 www. 字元。

  • 第三部份匹配許多字元。

  • 一個寫的不好的正規表式,就可以導致cpu使用率一下子飈升。

    其實這裏導致 CPU 使用率高的關鍵原因就是:Java 正規表式使用的引擎實作是 NFA自動機 ,這種正規表式引擎在進行字元匹配時會發生 回溯

    而一旦發生回溯,那其消耗的時間就會變得很長,有可能是幾分鐘,也有可能是幾個小時,時間長短取決於回溯的次數和復雜度。

    我們寫的正規表式,要盡量減少回溯。

    8 耗時計算

    有時候,我們的業務系統需要即時計算數據,比如:電商系統中需要即時計算優惠後的最終價格。

    或者需要在程式碼中,從一堆數據中,統計匯總出我們所需要的數據。

    如果這個即時計算或者即時統計的場景,是一個非常耗時的操作,並且該場景的請求並行量還不小。

    就可能會導致cpu飆高。

    因為即時計算需要消耗cpu資源,如果一直計算,就會一直消耗cpu資源。


    👇🏻 點選下方閱讀原文,獲取魚皮往期編程幹貨。

    往期推薦