戰(zhàn)指南)
簡(jiǎn)介面向Linux運(yùn)維工程師的高CPU占用問(wèn)題排查實(shí)戰(zhàn)文檔聚焦系統(tǒng)負(fù)載飆高時(shí)的定位與處理思路。資源系統(tǒng)梳理兩種常用排查方法一種通過(guò)top排序后結(jié)合top -H、線程ID轉(zhuǎn)十六進(jìn)制、jstack查看線程狀態(tài)另一種通過(guò)ps -mp獲取線程耗時(shí)并排序再結(jié)合jstack打印堆棧二者均能快速鎖定異常線程與相關(guān)代碼。文檔還融入真實(shí)生產(chǎn)案例演示Java進(jìn)程CPU占用率達(dá)到300%時(shí)的完整排查過(guò)程從初始top發(fā)現(xiàn)PID到最終定位線程堆棧避免盲目重啟服務(wù)同時(shí)介紹Zabbix、Nagios、阿里云監(jiān)控及“王教授”運(yùn)維工具的告警機(jī)制強(qiáng)調(diào)監(jiān)控前置與主動(dòng)發(fā)現(xiàn)的價(jià)值。資源為單個(gè)PDF文件大小147KB內(nèi)容精煉、操作命令明確適合運(yùn)維人員和后端開(kāi)發(fā)者隨時(shí)查閱文中方法無(wú)需圖形界面可直接在純命令行環(huán)境實(shí)踐。已有4448人學(xué)習(xí)下載對(duì)日常系統(tǒng)維護(hù)和故障應(yīng)急具有實(shí)際參考意義。1. CPU占用率高的排查先定位再動(dòng)手別把玄學(xué)當(dāng)性能分析Linux系統(tǒng)中CPU占用率偏高可能是運(yùn)維群里出現(xiàn)頻率最高的告警。很多人的第一反應(yīng)是登錄服務(wù)器敲top看到哪個(gè)進(jìn)程%CPU高就kill -9哪個(gè)這套流程在壓測(cè)環(huán)境能交差到了生產(chǎn)環(huán)境經(jīng)常會(huì)翻車(chē)同一個(gè)進(jìn)程的 CPU 統(tǒng)計(jì)跟監(jiān)控平臺(tái)對(duì)不上或者機(jī)器 load 已經(jīng)快拉滿top里 CPU 占用卻不到 60%。本文把一套在服務(wù)器上反復(fù)用的排查思路拆開(kāi)講怎么從進(jìn)程定位到線程、從用戶態(tài)挖到內(nèi)核態(tài)、再?gòu)默F(xiàn)場(chǎng)判斷該調(diào)業(yè)務(wù)還是調(diào)資源適合負(fù)責(zé)后臺(tái)服務(wù)和容器平臺(tái)的開(kāi)發(fā)運(yùn)維也適合準(zhǔn)備 linux 面試題的同學(xué)在紙上把整套流程推演一遍。2. 用 top、ps、mpstat 把高占用進(jìn)程和線程揪出來(lái)2.1 top 里的三個(gè)數(shù)%CPU、load average、us/sy 的正確讀法top是大家最熟的 linux 常用命令但多數(shù)人只看了兩個(gè)數(shù)第一行的 load average 和進(jìn)程列表里的%CPU。這兩個(gè)數(shù)恰恰最容易誤讀。load average 后面的三個(gè)值分別代表過(guò)去 1 分鐘、5 分鐘、15 分鐘的平均活躍進(jìn)程數(shù)?;钴S進(jìn)程包含正在運(yùn)行的 R 狀態(tài)進(jìn)程和等待 IO 的 D 狀態(tài)進(jìn)程所以 load 高不等于 CPU 高。一臺(tái) 4 核機(jī)器如果 load 是 4.0大致可以認(rèn)為跑滿但如果進(jìn)程都在等磁盤(pán)load 到 8 也可能%CPU只有 20%。判斷是否 CPU 問(wèn)題要以 CPU 狀態(tài)行和%CPU為準(zhǔn)load 只用來(lái)感受整體壓力趨勢(shì)。CPU 狀態(tài)行里重點(diǎn)看 us(user)、sy(system)、wa(iowait)、si(softirq)、st(steal)。us 高是業(yè)務(wù)代碼消耗sy 高是系統(tǒng)調(diào)用或內(nèi)核態(tài)消耗wa 高要轉(zhuǎn)向磁盤(pán)排查si 高要懷疑網(wǎng)絡(luò)包處理st 高則要考慮虛擬化宿主機(jī)搶占。進(jìn)入top后先按數(shù)字鍵1把 CPU 展開(kāi)成每個(gè)物理核的視圖如果只有個(gè)別核打滿說(shuō)明問(wèn)題可能出在單線程或中斷綁核后續(xù)排查方向完全不一樣。進(jìn)程列表默認(rèn)按 CPU 排序但按P可以強(qiáng)制排序按H可以切換成線程視圖按c顯示完整命令行。這里花一分鐘記住top -H -p PID這個(gè)組合它會(huì)直接進(jìn)入某個(gè)進(jìn)程的線程視圖是定位單線程熱點(diǎn)最快的入口。2.2 ps 的 %CPU 只是平均值線程級(jí)視角要交給 pidstat很多新手只靠ps aux看 CPU這是第一個(gè)坑ps 報(bào)告的是進(jìn)程從啟動(dòng)到現(xiàn)在的平均 CPU 占用不是當(dāng)前實(shí)時(shí)值。一個(gè)剛崩潰重啟的進(jìn)程哪怕現(xiàn)在把 CPU 打滿ps里也可能只顯示個(gè)位數(shù)百分比。所以ps適合拿來(lái)確認(rèn)進(jìn)程存在、PID、父子關(guān)系不適合判斷當(dāng)前熱點(diǎn)。我一般會(huì)分兩步。第一步用ps做粗篩把所有進(jìn)程按 CPU 占用排序看一眼到底哪個(gè)進(jìn)程可疑ps -eo pid,ppid,%cpu,%mem,user,comm --sort-%cpu | head -n 20-e表示所有進(jìn)程-o指定輸出列--sort-%cpu按 CPU 占用從高到低排序。這條命令 1 秒出結(jié)果適合在 CPU 飆高時(shí)先用它定一個(gè)嫌疑進(jìn)程再用下面這條看該進(jìn)程內(nèi)部的線程pidstat -u -t -p 12345 1 5pidstat來(lái)自 sysstat 包-u表示監(jiān)控 CPU-t顯示線程級(jí)別詳情-p 12345指定進(jìn)程 PID最后的1 5表示每秒采樣一次、共采樣 5 次。輸出里每行對(duì)應(yīng)一個(gè)線程%usr是用戶態(tài)占用%system是內(nèi)核態(tài)占用%CPU是線程整體占用TID是線程號(hào)。這樣定位出來(lái)的線程號(hào)可以直接對(duì)照jstack、perf的調(diào)用棧結(jié)果把問(wèn)題從“進(jìn)程層面”壓到“線程層面”。這里有個(gè)很重要的換算概念單個(gè)線程的%CPU上限就是 100%相當(dāng)于占滿一個(gè)邏輯核一個(gè)進(jìn)程顯示 300%說(shuō)明它內(nèi)部有 3 個(gè)線程在并行跑滿。后續(xù)不管用perf還是看 Java 線程棧都要以 TID 為單位去對(duì)而不是用 PID。2.3 mpstat 看核間分布單核打滿和全核打滿是兩種病判斷完線程下一步要知道 CPU 壓力是不是均勻分布在所有核上。這決定了你要查的是單線程代碼問(wèn)題、綁核問(wèn)題還是多線程并發(fā)資源耗盡。命令是mpstatmpstat -P ALL 1 3-P ALL展示所有 CPU 核1 3表示每秒輸出一次、輸出三次。返回的表格里每一行是一個(gè) CPU 核的實(shí)時(shí)占用%usr、%sys、%irq、%soft、%steal分別對(duì)應(yīng)不同來(lái)源??吹降慕Y(jié)果通常歸成兩類(lèi)。一類(lèi)是某個(gè)核長(zhǎng)期超過(guò) 90%其他核卻很悠閑這種大概率是單線程的邏輯寫(xiě)在關(guān)鍵路徑上或者內(nèi)核把中斷都塞給了一個(gè)核另一類(lèi)是每個(gè)核都高這種要么是多線程死循環(huán)要么是鎖競(jìng)爭(zhēng)導(dǎo)致所有線程都在自旋等待要么是宿主機(jī) CPU 超賣(mài)。還有一種容易被忽略的情況業(yè)務(wù)進(jìn)程每個(gè)核都只有 30% 左右但 CPU 總占用加起來(lái)很高。這種分布常見(jiàn)于日志寫(xiě)入、壓縮、序列化這類(lèi)影子開(kāi)銷(xiāo)多的服務(wù)。不要只盯著最高的核看要按“總 CPU 時(shí)間消耗”去算賬。觀察現(xiàn)象可能原因下一步動(dòng)作單核打滿其他核空閑單線程密集計(jì)算、中斷綁核查代碼熱點(diǎn)、查 /proc/interrupts所有核同步打滿多線程死循環(huán)、鎖競(jìng)爭(zhēng)perf 采樣看內(nèi)核態(tài)函數(shù)各核 20%~40% 但總量高日志、壓縮、序列化等影子開(kāi)銷(xiāo)逐一核對(duì)輔助進(jìn)程與內(nèi)核線程%steal 明顯偏高虛擬機(jī) CPU 被宿主機(jī)搶占檢查云主機(jī)規(guī)格與鄰居負(fù)載3. 從進(jìn)程態(tài)挖到內(nèi)核態(tài)perf、vmstat 與中斷風(fēng)暴的排查路徑3.1 perf top 看熱點(diǎn)函數(shù)數(shù)據(jù)比直覺(jué)靠譜進(jìn)程和線程定位完了只知道“誰(shuí)”在燒 CPU還不知道“為什么”。這時(shí)候再猜就屬于玄學(xué)直接把采樣工具掛上去看內(nèi)核和用戶態(tài)函數(shù)分布。最簡(jiǎn)單的是perf topperf top -p 12345它會(huì)按 CPU 采樣頻率對(duì)當(dāng)前進(jìn)程的熱點(diǎn)函數(shù)實(shí)時(shí)排序默認(rèn)帶調(diào)用棧展開(kāi)功能??吹接脩魬B(tài)函數(shù)名基本能判斷是不是死循環(huán)看到內(nèi)核態(tài)函數(shù)名也能猜個(gè)大概方向。比如native_queued_spin_lock_slowpath出現(xiàn)頻率很高多半是自旋鎖競(jìng)爭(zhēng)do_softirq相關(guān)函數(shù)高要轉(zhuǎn)向網(wǎng)絡(luò)包處理finish_task_switch高說(shuō)明線程切換本身消耗了大量 CPU進(jìn)程內(nèi)線程數(shù)可能太多。如果希望保存下來(lái)慢慢分析可以用采樣方式perf record -g -p 12345 -- sleep 10 perf report-g記錄調(diào)用棧-p指定進(jìn)程-- sleep 10表示采 10 秒就自動(dòng)結(jié)束。perf report進(jìn)入交互界面后會(huì)生成一張熱點(diǎn)調(diào)用樹(shù)按回車(chē)逐級(jí)展開(kāi)能看到完整調(diào)用鏈。這條鏈路比任何日志都能說(shuō)明問(wèn)題。需要注意兩點(diǎn)。第一內(nèi)核符號(hào)可能需要 root 權(quán)限才能讀全普通用戶經(jīng)??吹揭欢裑unknown]生產(chǎn)環(huán)境可以臨時(shí)用 root 執(zhí)行受限命令后退出不要長(zhǎng)期掛 root daemon。第二Java、Go 這類(lèi) JIT 或運(yùn)行時(shí)語(yǔ)言perf 直接看到的用戶態(tài)符號(hào)往往是運(yùn)行時(shí)自身配合jstack或py-spy把 TID 翻譯成業(yè)務(wù)線程名才算是閉環(huán)。3.2 vmstat 的 r 列和 cs 列上下文切換為什么會(huì)飆升perf能看函數(shù)級(jí)但很多機(jī)房環(huán)境不方便裝額外工具此時(shí)vmstat是個(gè)不需要安裝的備選。執(zhí)行vmstat 1 5關(guān)注五列r、cs、us、sy、wa。r 是當(dāng)前處于可運(yùn)行狀態(tài)的進(jìn)程數(shù)如果這個(gè)值持續(xù)大于 CPU 核數(shù)說(shuō)明有一批進(jìn)程在排隊(duì)CPU 確實(shí)不夠用cs 是每秒上下文切換次數(shù)這個(gè)值正常情況下每核幾百到一千多超過(guò)兩三萬(wàn)就要警惕進(jìn)程線程可能像抽風(fēng)一樣在搶時(shí)間片us 和 sy 分別是用戶態(tài)與內(nèi)核態(tài)的 CPU 時(shí)間占比wa 是 IO 等待占比。我最常碰到的場(chǎng)景是CPU 占用看著不高us 只有 20%但 sy 占了 40%cs 數(shù)值異常高。這種往往是短生命周期線程大量創(chuàng)建銷(xiāo)毀或者定時(shí)器、信號(hào)觸發(fā)太頻繁導(dǎo)致 CPU 大量浪費(fèi)在“切換”而不是“干活”上。排查時(shí)用pidstat -w -t -p PID 1 5看每個(gè)線程的 cswch/s(自愿切換) 和 nvcswch/s(非自愿切換)自愿切換過(guò)多說(shuō)明線程在等待鎖或 IO非自愿切換過(guò)多說(shuō)明時(shí)間片不夠、線程數(shù)超過(guò)核數(shù)太多。3.3 把 D 狀態(tài)進(jìn)程和 iowait 從 CPU 占用里剝出去很多人把 load 高和 CPU 高混為一談?wù)鎸?shí)世界里有大量 load 很高但 CPU 閑置的場(chǎng)景。罪魁禍?zhǔn)淄ǔJ?D 狀態(tài)進(jìn)程也就是不可中斷睡眠狀態(tài)典型情況是進(jìn)程在等磁盤(pán) IO 或 NFS 響應(yīng)。D 狀態(tài)進(jìn)程會(huì)計(jì)入 load average但不消耗 CPU 時(shí)間片所以top里 CPU 占用看著不高機(jī)器卻卡得不行。排查命令很簡(jiǎn)單ps -eo pid,stat,wchan:30,comm | awk $2 ~ /^D/ {print}wchan:30顯示進(jìn)程當(dāng)前等待的內(nèi)核函數(shù)名能看到它究竟卡在哪個(gè)內(nèi)核函數(shù)上。比如卡在wait_on_page_bit說(shuō)明在等內(nèi)存頁(yè)回寫(xiě)卡在nfs相關(guān)函數(shù)說(shuō)明遠(yuǎn)端存儲(chǔ)有問(wèn)題??吹揭欢?D 狀態(tài)進(jìn)程時(shí)不要再調(diào) CPU 配置先去查磁盤(pán)iostat -x 1看%util和svctm或者確認(rèn) NFS 掛載點(diǎn)是否可達(dá)。3.4 中斷風(fēng)暴ksoftirqd 和網(wǎng)卡多隊(duì)列還有一種 CPU 高是中斷帶來(lái)的表面上找不到用戶態(tài)進(jìn)程但top里 si(軟中斷) 或者 hi(硬中斷) 不低同時(shí) ksoftirqd 內(nèi)核線程占用會(huì)飆上去。這個(gè)場(chǎng)景在流量型服務(wù)上很常見(jiàn)網(wǎng)卡每收一個(gè)包就觸發(fā)一次中斷如果小包特別多中斷處理本身吃掉的 CPU 可能比業(yè)務(wù)代碼還多。先看中斷是否集中在某個(gè)核上cat /proc/interrupts左側(cè)是中斷號(hào)各列對(duì)應(yīng)每個(gè) CPU 核收到的中斷次數(shù)。如果網(wǎng)卡相關(guān)中斷號(hào)只在 CPU0 上漲說(shuō)明當(dāng)前驅(qū)動(dòng)或配置把所有網(wǎng)絡(luò)中斷都綁到了第一個(gè)核這就是單核被打滿但其他核空閑的原因之一。常見(jiàn)做法是先確認(rèn)網(wǎng)卡是否支持多隊(duì)列用網(wǎng)卡工具查看隊(duì)列數(shù)量然后調(diào)整隊(duì)列數(shù)與 CPU 核數(shù)匹配讓多個(gè)核分?jǐn)偸瞻袛?。如果是虛擬化環(huán)境且驅(qū)動(dòng)不支持多隊(duì)列可以嘗試開(kāi)啟 RPS/RFS 這類(lèi)軟件分發(fā)機(jī)制讓軟中斷均勻分散到各核避免單核成為瓶頸。這類(lèi)問(wèn)題的后續(xù)驗(yàn)證也很直接mpstat -P ALL里各核%soft是否趨于均勻且用戶態(tài)業(yè)務(wù) CPU 是否順勢(shì)降下來(lái)。4. 對(duì)高 CPU 問(wèn)題的常見(jiàn)處置手段從鎖競(jìng)爭(zhēng)到進(jìn)程配額4.1 死循環(huán)和忙等從熱點(diǎn)函數(shù)到止損措施代碼里出現(xiàn)死循環(huán)或忙等是 CPU 高最直接的原因。perf top顯示某個(gè)用戶態(tài)函數(shù)長(zhǎng)期壓在頂部基本就能下結(jié)論。處理順序應(yīng)該是先止損、再定位、后修復(fù)。止損手段要看服務(wù)形態(tài)。能快速回滾的版本直接回滾不能回滾的臨時(shí)把并發(fā)調(diào)低比直接 kill 進(jìn)程更安全因?yàn)?kill 會(huì)中斷所有請(qǐng)求影響面更大。常見(jiàn)做法是先用kill -STOP PID暫停進(jìn)程確認(rèn)現(xiàn)場(chǎng)影響范圍后再?zèng)Q定是否kill -TERM。kill -9是最后的選擇因?yàn)樗唤o進(jìn)程清理連接和釋放內(nèi)存的機(jī)會(huì)可能留下半關(guān)閉的 socket 或臟數(shù)據(jù)。止血后再按語(yǔ)言選工具C/C 用perf看調(diào)用棧Java 服務(wù)用jstack -l PID抓線程棧多抓幾次對(duì)比是不是同一個(gè)方法卡住Python 服務(wù)可用py-spy dump --pid PID拿到當(dāng)前正在執(zhí)行的 Python 函數(shù)名。定位到具體函數(shù)后再改代碼不要不停重啟重啟只是把黑匣子又蓋回去了。4.2 鎖競(jìng)爭(zhēng)線程數(shù)不是越大越好另一個(gè)高發(fā)原因是鎖競(jìng)爭(zhēng)?,F(xiàn)象很典型CPU 占用高業(yè)務(wù)吞吐卻上不去perf top中自旋鎖相關(guān)函數(shù)排在前列火焰圖里鎖等待區(qū)域又寬又矮。很多人第一反應(yīng)是加線程實(shí)際上在高 CPU 密集場(chǎng)景下線程數(shù)遠(yuǎn)超核數(shù)就會(huì)產(chǎn)生大量上下文切換和鎖自旋CPU 全部耗在等鎖上活沒(méi)干多少。這類(lèi)問(wèn)題要先看鎖的粒度。把大鎖拆成細(xì)鎖、用讀寫(xiě)鎖代替互斥鎖、減少持鎖期間的操作都是常用手段。線程池大小也不是簡(jiǎn)單 2 倍核數(shù)就合理CPU 密集場(chǎng)景通常設(shè)為核數(shù)或核數(shù)加一IO 密集場(chǎng)景再放寬。這個(gè)問(wèn)題也是 linux 面試題里反復(fù)出現(xiàn)的給你一個(gè)四核機(jī)器線程池開(kāi) 100 個(gè)線程跑純計(jì)算吞吐能提升嗎答案是不能反而可能因?yàn)殒i競(jìng)爭(zhēng)和切換開(kāi)銷(xiāo)讓性能下降。如果代碼一時(shí)改不動(dòng)可以先用taskset把進(jìn)程綁定到固定核上限制它與其他進(jìn)程爭(zhēng)搶但這只適用于單實(shí)例或?qū)ρ舆t不敏感的服務(wù)多實(shí)例場(chǎng)景慎用。4.3 日志滾輪和壓縮讓日志進(jìn)程成了 CPU 大戶很多 CPU 高的問(wèn)題不在業(yè)務(wù)進(jìn)程而在日志鏈路上。新裝的 Linux 系統(tǒng)更容易踩這個(gè)坑systemd-journald 默認(rèn)把每個(gè)服務(wù)的輸出都收進(jìn) journal如果某個(gè)程序瘋狂打日志journald 的 CPU 會(huì)先飆起來(lái)。接著是舊日志壓縮歸檔日志越大壓縮進(jìn)程消耗越高。排查時(shí)別只盯業(yè)務(wù)進(jìn)程把systemd-journald、rsyslogd、logrotate的 CPU 都看一遍pidstat -u -p $(pidof systemd-journald) 1 5如果 journald 占用顯著先看是不是有服務(wù)一條條刷屏把日志級(jí)別調(diào)上去或者把無(wú)關(guān)輸出重定向到 /dev/null。journald 側(cè)可以調(diào)整/etc/systemd/journald.conf里的SystemMaxUse限制總?cè)罩救萘縍ateLimitIntervalSec和RateLimitBurst控制單位時(shí)間最多接收多少條日志修改后重啟 journald 生效。rsyslog 同步寫(xiě)盤(pán)壓力大時(shí)可以改成異步隊(duì)列或者按日志量調(diào)整滾動(dòng)策略避免一個(gè)日志文件膨脹到幾百兆后再一次性壓縮。這個(gè)環(huán)節(jié)經(jīng)常被忽略屬于排查順序里性價(jià)比很高的一步。4.4 用 systemd 配額給失控進(jìn)程設(shè)上限當(dāng)進(jìn)程暫時(shí)殺不得、代碼又改不動(dòng)可以先把它的 CPU 占用限制住保住同一臺(tái)機(jī)器上其他服務(wù)。systemd 管理服務(wù)可以直接用運(yùn)行時(shí)命令systemctl set-property your-service.service CPUQuota150%CPUQuota150%表示最多使用 1.5 個(gè)核的 CPU 資源底層對(duì)應(yīng) cgroup 的 cpu.max 配額150% 實(shí)際是 150000/100000 的比例關(guān)系。設(shè)置后立即生效重啟也會(huì)保留。想臨時(shí)看一下效果再?zèng)Q定去留可以執(zhí)行同樣的命令把配額改回空值去除限制。對(duì)不在 systemd 管理下的進(jìn)程可以用cpulimit -p PID -l 50這類(lèi)用戶態(tài)工具它通過(guò)暫停和恢復(fù)進(jìn)程來(lái)控制 CPU適合應(yīng)急但不適合精度要求高的場(chǎng)景。要記住配額只是止損不是修復(fù)長(zhǎng)時(shí)間用配額壓著有問(wèn)題的進(jìn)程會(huì)讓延遲劣化且難以察覺(jué)還是要留出時(shí)間窗處理根因。4.5 虛擬機(jī) CPU steal宿主機(jī)搶走了本該屬于你的 CPU云服務(wù)器和虛擬化環(huán)境里還有一種“假高占用”的根因不在你機(jī)器內(nèi)部而在宿主機(jī)超賣(mài)。mpstat輸出里的%steal字段或者top狀態(tài)行里的st代表虛擬機(jī)申請(qǐng) CPU 時(shí)間但被宿主機(jī)調(diào)度延后的比例。如果%steal持續(xù)超過(guò) 10%應(yīng)用的 CPU 占用和延遲都會(huì)肉眼可見(jiàn)地惡化但你在虛機(jī)里查任何進(jìn)程都查不出問(wèn)題。這種場(chǎng)景的處理思路很簡(jiǎn)單不要調(diào)業(yè)務(wù)參數(shù)先調(diào)整部署位置。常見(jiàn)做法是遷移到低負(fù)載宿主機(jī)或者在購(gòu)買(mǎi)資源時(shí)選擇限制超賣(mài)比例的實(shí)例規(guī)格。如果服務(wù)跨多臺(tái)機(jī)器優(yōu)先把流量切走如果無(wú)法遷移可以嘗試把業(yè)務(wù)線程的調(diào)度優(yōu)先級(jí)調(diào)低減少被宿主機(jī)搶占時(shí)的連鎖抖動(dòng)但這個(gè)措施效果有限。判斷 CPU 高之前先看%steal能省掉后面一整輪業(yè)務(wù)層排查。5. CPU占用率排查避坑5 個(gè)容易翻車(chē)的誤判場(chǎng)景5.1 top 顯示占用正常但機(jī)器整體卡成 PPT現(xiàn)象top里各核%CPU只有 20% 左右但敲命令明顯卡頓SSH 窗口響應(yīng)延遲好幾十秒。原因CPU 狀態(tài)行里的 us、sy 都不高時(shí)要回看 load average 和 wa。常見(jiàn)根因是磁盤(pán) IO 飽和進(jìn)程大量進(jìn)入 D 狀態(tài)排進(jìn) run queueload 被頂高而 CPU 真正花在“等 IO”上而不是“算”上CPU 百分比自然不高。解決用iostat -x 1看磁盤(pán)%util用ps -eo pid,stat | grep D數(shù) D 狀態(tài)進(jìn)程確認(rèn)后再?zèng)Q定擴(kuò)容磁盤(pán)、遷移數(shù)據(jù)還是排查慢存儲(chǔ)。不要把突破點(diǎn)放在 CPU 上那條路是死的。5.2 一個(gè)進(jìn)程顯示 300% 或 800%以為是 bug 或中毒現(xiàn)象進(jìn)程列表里某個(gè)進(jìn)程%CPU顯示超過(guò) 100%甚至 900%第一反應(yīng)是數(shù)值異?;蜻M(jìn)程被種了礦機(jī)。原因top和pidstat的%CPU都是按核歸一化的100% 代表一個(gè)邏輯核打滿。多線程進(jìn)程的多個(gè)線程并行運(yùn)行占用多個(gè)核總和超過(guò) 100% 是完全正常的。解決用top -H -p PID切到線程視圖確認(rèn)是多個(gè)線程各占一部分還是某個(gè)線程單獨(dú)占滿。按 H 切換后如果所有線程加起來(lái)和進(jìn)程總占用對(duì)得上說(shuō)明是并行計(jì)算而非異常如果單個(gè)線程超過(guò) 100%反而要先懷疑進(jìn)程綁核或者多個(gè)線程綁定同一個(gè)核再去查業(yè)務(wù)是否正常。5.3 strace 一掛上去CPU 占用反而降了現(xiàn)象現(xiàn)場(chǎng)怎么看怎么可疑strace 跟蹤幾秒后CPU 占用以肉眼可見(jiàn)速度下降讓人以為是誤報(bào)。原因strace 會(huì)攔截每次系統(tǒng)調(diào)用讓原本高頻的系統(tǒng)調(diào)用變慢整個(gè)程序的執(zhí)行節(jié)奏被拉低CPU 占用自然降下來(lái)。這不代表問(wèn)題自動(dòng)消失是干擾工具改變了測(cè)量對(duì)象。解決遇到這種情況就不要再用strace -p PID做長(zhǎng)時(shí)跟蹤改用短窗口采集比如timeout 5 strace -f -c -p PID只看匯總統(tǒng)計(jì)或者換用perf采樣它對(duì)程序時(shí)序的影響小得多。記住排查工具的副作用本身也是排查的一部分。5.4 監(jiān)控平臺(tái)顯示 CPU 90%登錄服務(wù)器看卻不到 30%現(xiàn)象監(jiān)控告警 CPU 持續(xù) 90%但 SSH 上去執(zhí)行top和mpstat占用率只有 30% 左右兩邊數(shù)據(jù)對(duì)不上。原因監(jiān)控平臺(tái)通常按 1 分鐘或 5 分鐘周期聚合會(huì)把高峰期和低峰期平均top看的是當(dāng)前瞬間值兩者采樣窗口不同本來(lái)就有差異。另一個(gè)常見(jiàn)原因是監(jiān)控統(tǒng)計(jì)的是 cgroup 視圖而top看的是整機(jī)視圖容器場(chǎng)景尤其明顯。解決先把自己采集的周期拉長(zhǎng)用pidstat -u 1 60連續(xù)記錄一分鐘看平均 CPU 和監(jiān)控平臺(tái)是否接近再確認(rèn)容器里用top時(shí)是否已經(jīng)進(jìn)入了對(duì)應(yīng)容器的 PID namespace。數(shù)據(jù)對(duì)不齊時(shí)以長(zhǎng)時(shí)間連續(xù)采樣為準(zhǔn)不要拿一個(gè)瞬間值反駁監(jiān)控平臺(tái)。5.5 內(nèi)存回收被誤判為 CPU 問(wèn)題現(xiàn)象us 和 sy 都不高但 kswapd 內(nèi)核線程 CPU 起高系統(tǒng)整體響應(yīng)也變慢。原因內(nèi)存接近上限時(shí)內(nèi)核要反復(fù)掃描內(nèi)存頁(yè)做回收這個(gè)過(guò)程消耗 CPU 時(shí)間還會(huì)觸發(fā)直接回收進(jìn)而阻塞進(jìn)程。CPU 高只是表面現(xiàn)象根因在內(nèi)存。解決順手看free -h和vmstat的 si、so 兩列si/so 持續(xù)不為零說(shuō)明已經(jīng)發(fā)生換頁(yè)。用dmesg -T | tail看有沒(méi)有 oom 相關(guān)記錄有的話優(yōu)先處理內(nèi)存泄漏和調(diào)低緩存上限而不是給 CPU 加配。內(nèi)存問(wèn)題偽裝成 CPU 問(wèn)題的比例不低CPU 排查的前三個(gè)步驟里應(yīng)該帶一眼內(nèi)存。6. 把排查流程固化成一個(gè)采集腳本再對(duì)照驗(yàn)證結(jié)果第一次排查 CPU 高時(shí)憑記憶敲命令容易漏項(xiàng)等現(xiàn)場(chǎng)過(guò)去了再補(bǔ)采集就晚了。我現(xiàn)在養(yǎng)成的習(xí)慣是提前在機(jī)器上備一個(gè)采集腳本任何一個(gè)環(huán)節(jié)告警都先讓它抓現(xiàn)場(chǎng)。以下這個(gè)腳本會(huì)在當(dāng)前目錄生成帶時(shí)間戳的快照文件涵蓋 load、CPU 狀態(tài)、進(jìn)程排行、線程排行四個(gè)維度#!/bin/bash stamp$(date %Y%m%d_%H%M%S) outdir/tmp/cpu_debug mkdir -p $outdir { echo snapshot $stamp uptime echo ----- CPU state ----- top -bn1 | head -n 8 echo ----- process top ----- ps -eo pid,ppid,%cpu,%mem,user,comm --sort-%cpu | head -n 15 echo ----- thread top ----- pidstat -u -t 1 1 | sort -k 9 -r | head -n 15 } $outdir/snapshot_${stamp}.txt echo saved to $outdir/snapshot_${stamp}.txttop -bn1是非交互模式只取一次快照pidstat -u -t 1 1采樣一秒鐘內(nèi)的線程占用用sort -k 9 -r按第 9 列%CPU排序。腳本要在疑似 CPU 高的第一時(shí)間執(zhí)行越早越能抓到當(dāng)時(shí)的真實(shí)負(fù)載而不是事后補(bǔ)測(cè)的近似值。如果問(wèn)題持續(xù)而反復(fù)還可以用一段時(shí)間的趨勢(shì)記錄替代快照pidstat -u -t -p 12345 5 120 /tmp/cpu_debug/pidstat_$stamp.log 每 5 秒采一次、持續(xù) 10 分鐘后臺(tái)運(yùn)行對(duì)比不同時(shí)段的線程熱點(diǎn)變化能看出是持續(xù)打滿還是周期性脈沖。驗(yàn)證解決效果時(shí)不要只比較單個(gè)進(jìn)程的 CPU 百分比要對(duì)照三組指標(biāo)負(fù)載狀態(tài)下降、上下文切換 cs 回落、應(yīng)用側(cè)延遲或吞吐恢復(fù)。CPU 占用降下來(lái)但請(qǐng)求延遲更差了說(shuō)明只是把熱點(diǎn)壓住、根因沒(méi)動(dòng)。我現(xiàn)在每次調(diào)整完配置都會(huì)先抓十分鐘的pidstat記錄和修復(fù)前的數(shù)據(jù)并排看確認(rèn)業(yè)務(wù)指標(biāo)也一起好轉(zhuǎn)才算結(jié)束。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取