服務器 CPU 飆高(CPU 使用率持續超過 80% 甚至接近 100%)是典型的性能瓶頸問題,可能由應用邏輯缺陷、資源競爭、外部壓力或硬件/系統異常引起。以下是系統化的排查步驟,覆蓋從現象確認到根因定位的全流程。
?一、確認 CPU 飆高的現象與范圍?
首先需明確 CPU 高負載的具體表現和影響范圍,避免誤判:
?1. 區分用戶態(User)與內核態(System)CPU?
通過 top
或 htop
查看 CPU 使用率細分:
- ?用戶態(User)??:應用程序代碼執行占用的 CPU(如業務邏輯、計算)。
- ?內核態(System)??:操作系統內核執行占用的 CPU(如 I/O 調度、網絡協議棧、進程調度)。
?關鍵結論?:
- 若用戶態 CPU 高:問題通常在應用程序(如死循環、頻繁計算)。
- 若內核態 CPU 高:問題可能在 I/O 阻塞、網絡流量過大或系統調用頻繁。
?2. 確認是持續高負載還是間歇性高負載?
- ?持續高負載?:可能是應用邏輯缺陷(如死循環)、內存泄漏導致頻繁 GC,或外部持續壓力(如 DDoS 攻擊)。
- ?間歇性高負載?:可能是批量任務(如定時任務)、突發流量(如秒殺活動)或資源競爭(如鎖爭用)。
?3. 定位高負載的進程/線程?
使用工具鎖定具體是哪個進程或線程占用了大量 CPU:
??(1) 系統級工具:top/htop?
top
命令:按P
鍵按 CPU 使用率排序,找到占用最高的進程(PID)。top -c # 顯示完整命令行
htop
(更友好的交互式工具):支持樹形查看進程關系,按F6
選擇排序方式(如 CPU 占用)。
??(2) 進程級工具:pidstat?
通過 pidstat
查看進程的 CPU 使用細節(需安裝 sysstat
包):
pidstat -u 1 5 # 每 1 秒輸出一次,共 5 次,顯示進程的 CPU 使用率
??(3) 線程級工具:top -H 或 jstack(Java 應用)??
- ?非 Java 應用?:通過
top -H -p <PID>
查看進程內的線程 CPU 占用(-H
顯示線程)。 - ?Java 應用?:使用
jstack <PID>
生成線程轉儲,結合grep
過濾阻塞或運行中的線程:jstack <PID> | grep -A 20 "java.lang.Thread.State: RUNNABLE" # 查看運行中的線程
?二、分析高 CPU 進程的具體原因?
鎖定高 CPU 進程后,需深入分析其內部邏輯或外部交互,常見原因及排查方法如下:
?1. 應用程序邏輯缺陷(用戶態 CPU 高)??
?典型場景?:死循環、遞歸未終止、頻繁計算(如加密/壓縮)、錯誤的分頁查詢(全表掃描)。
??(1) Java 應用:檢查線程狀態與調用棧?
?死循環/無限遞歸?:線程轉儲中若大量線程處于
RUNNABLE
狀態,且調用棧顯示重復方法(如while(true)
循環),可能是死循環。
示例(死循環):"http-nio-8080-exec-1" #12 prio=5 os_prio=0 tid=0x00007f8e3c0b8000 nid=0x4567 runnable [0x00007f8e2c1fe000]java.lang.Thread.State: RUNNABLEat com.example.DeadlockDemo.infiniteLoop(DeadlockDemo.java:20) # 重復調用自身...
?頻繁 GC?:通過
jstat -gcutil <PID>
查看 GC 頻率(FGC
頻繁且GCT
占比高),可能是內存泄漏或大對象分配導致。
??(2) 非 Java 應用:檢查進程調用棧?
使用 strace
(Linux)跟蹤進程的系統調用,定位耗時操作:
strace -p <PID> -c # 統計系統調用耗時(-c 顯示統計)
若發現大量 read()
、write()
或 poll()
調用,可能是 I/O 阻塞或網絡流量過大。
?2. I/O 阻塞導致內核態 CPU 高?
?典型場景?:磁盤 I/O 慢(如機械盤 vs SSD)、網絡 I/O 阻塞(如大量未完成的 Socket 連接)、文件描述符耗盡。
??(1) 磁盤 I/O 問題?
- 使用
iostat
查看磁盤 I/O 利用率(%util
接近 100% 表示磁盤滿負荷):iostat -d 1 5 # 每 1 秒輸出一次磁盤 I/O 統計
- 若
%util
高且await
(平均 I/O 等待時間)大,可能是磁盤性能不足或存在大量隨機讀寫(如數據庫日志寫入)。
??(2) 網絡 I/O 問題?
- 使用
netstat
或ss
查看網絡連接狀態(如大量TIME_WAIT
或ESTABLISHED
連接):ss -ant | grep ESTAB # 查看已建立的 TCP 連接數
- 若連接數過多(如超過
ulimit -n
限制),可能導致進程阻塞在accept()
或connect()
,間接推高內核態 CPU。
?3. 鎖競爭與線程爭用?
?典型場景?:多個線程競爭同一把鎖(如 synchronized
、ReentrantLock
),導致線程頻繁阻塞/喚醒。
??(1) Java 應用:檢查鎖競爭?
- 使用
jstack
查看線程的鎖持有與等待關系(locked
和waiting to lock
字段):"Thread-1":waiting to lock <0x000000076b45a2c0> (a java.lang.Object)which is held by "Thread-0""Thread-0":locked <0x000000076b45a2c0> (a java.lang.Object)waiting to lock <0x000000076b45a290> (a java.lang.Object)
Thread-0
和Thread-1
互相等待對方持有的鎖,形成鎖競爭。
??(2) 非 Java 應用:使用 perf
分析?
Linux 下使用 perf
工具分析鎖競爭(需 root 權限):
perf top -p <PID> # 實時查看進程的熱點函數(如鎖獲取/釋放)
?4. 外部壓力:突發流量或惡意請求?
?典型場景?:DDoS 攻擊、批量任務(如定時任務并發執行)、爬蟲高頻訪問。
??(1) 檢查網絡流量?
- 使用
iftop
或nload
查看網絡帶寬占用(如某個 IP 發送大量請求):iftop -i eth0 # 實時顯示網絡接口流量
??(2) 檢查應用日志?
- 查看 Web 服務器(如 Nginx、Tomcat)的訪問日志,統計高頻請求的 URL 或 IP:
grep "GET /api" access.log | awk '{print $1}' | sort | uniq -c | sort -nr # 統計 IP 訪問次數
?5. 硬件或系統異常?
?典型場景?:CPU 本身故障(如過熱降頻)、BIOS 配置錯誤、內核模塊沖突。
??(1) 檢查 CPU 溫度與風扇?
- 使用
sensors
工具(需安裝lm-sensors
包)查看 CPU 溫度:sensors # 顯示 CPU 核心溫度(如 Core 0: +85°C)
??(2) 檢查內核日志?
查看 /var/log/syslog
或 /var/log/kern.log
,尋找硬件錯誤或異常:
dmesg | grep -i "error\|warning" # 顯示內核錯誤/警告信息
?三、總結:排查流程與工具鏈?
?步驟? | ?目標? | ?關鍵工具/命令? |
---|---|---|
確認 CPU 飆高現象 | 區分用戶態/內核態,定位進程/線程 | top 、htop 、pidstat |
分析高負載進程 | 鎖定具體應用或服務 | jstack (Java)、strace (非 Java) |
排查應用邏輯缺陷 | 死循環、頻繁計算、GC 問題 | 線程轉儲、jstat -gcutil 、代碼審查 |
排查 I/O 阻塞 | 磁盤/網絡 I/O 慢、連接數過多 | iostat 、ss 、netstat |
排查鎖競爭與線程爭用 | 線程互相等待鎖 | jstack (鎖持有/等待關系)、perf (非 Java) |
排查外部壓力 | 突發流量、惡意請求 | iftop 、nload 、訪問日志分析 |
檢查硬件/系統異常 | CPU 故障、散熱問題、內核錯誤 | sensors 、dmesg 、BIOS 配置檢查 |
?四、注意事項?
- ?生產環境謹慎操作?:避免在業務高峰期重啟進程或修改配置,優先通過監控工具(如 Prometheus+Grafana)實時觀察。
- ?日志與監控結合?:通過日志(如應用日志、系統日志)和監控(CPU、內存、磁盤 I/O)交叉驗證,避免誤判。
- ?逐步縮小范圍?:從系統→進程→線程→代碼/配置,逐層定位根因,避免盲目修改。
通過以上步驟,可高效排查服務器 CPU 飆高的問題,最終定位到具體的應用邏輯缺陷、資源競爭或外部壓力,并針對性優化。