線上 CPU 飆高最怕兩件事:一是盯著top看了半小時(shí),最后還是不知道是誰(shuí)打滿了核;二是誤把負(fù)載高當(dāng)成 CPU 高,處理動(dòng)作做反了,越處理越抖。生產(chǎn)環(huán)境里,CPU 問(wèn)題通常不是單一指標(biāo)異常,而是一條完整鏈路:業(yè)務(wù)請(qǐng)求放大、線程模型失控、內(nèi)核軟中斷堆積、磁盤(pán)或網(wǎng)絡(luò)抖動(dòng)把 CPU 拖進(jìn)系統(tǒng)態(tài),最后體現(xiàn)在告警平臺(tái)上只剩一行“CPU usage > 90%”。
我自己排這類(lèi)故障時(shí),不會(huì)一上來(lái)就改參數(shù),也不會(huì)先重啟服務(wù)。第一步永遠(yuǎn)是把現(xiàn)場(chǎng)固定住,先判斷是整機(jī) CPU 打滿、單進(jìn)程熱點(diǎn)、線程級(jí)死循環(huán)、內(nèi)核態(tài)消耗,還是虛擬化層 steal time。判斷對(duì)了,后面的命令基本就是順著證據(jù)走。
一、概述
1.1 背景介紹
Linux 服務(wù)器 CPU 飆高,常見(jiàn)表現(xiàn)有三類(lèi):
監(jiān)控中CPU usage連續(xù) 5 分鐘超過(guò) 85%,業(yè)務(wù) RT 明顯抬升
load average飆升,但應(yīng)用日志沒(méi)有大量報(bào)錯(cuò),看起來(lái)像“無(wú)聲故障”
單臺(tái)機(jī)器異常,重啟后短時(shí)間恢復(fù),過(guò)一陣又復(fù)發(fā)
這類(lèi)問(wèn)題本質(zhì)上是在回答四個(gè)問(wèn)題:
CPU 時(shí)間消耗在用戶態(tài)還是內(nèi)核態(tài)
是哪個(gè)進(jìn)程、哪個(gè)線程、哪段調(diào)用棧在吃 CPU
是業(yè)務(wù)代碼導(dǎo)致,還是網(wǎng)絡(luò)、中斷、磁盤(pán)、容器配額把 CPU 頂上去
現(xiàn)場(chǎng)止血之后,怎么避免再次發(fā)生
1.2 技術(shù)特點(diǎn)
分層排查:先看整機(jī),再看進(jìn)程,再看線程,再看調(diào)用棧,避免在錯(cuò)誤層級(jí)浪費(fèi)時(shí)間
證據(jù)驅(qū)動(dòng):每一步都保留命令輸出、時(shí)間點(diǎn)和 PID,復(fù)盤(pán)時(shí)能回放故障過(guò)程
兼容生產(chǎn)環(huán)境:優(yōu)先使用低侵入命令,只有在證據(jù)不足時(shí)才上perf、strace這類(lèi)更重的工具
1.3 適用場(chǎng)景
電商、支付、營(yíng)銷(xiāo)活動(dòng)高峰期間,某臺(tái)業(yè)務(wù)機(jī) CPU 持續(xù) 90% 以上
Java、Go、Python 服務(wù) RT 抬升,容器副本正常但單 Pod 熱點(diǎn)明顯
Nginx、網(wǎng)關(guān)、日志采集節(jié)點(diǎn)出現(xiàn)高系統(tǒng)態(tài) CPU,懷疑是軟中斷或連接風(fēng)暴
1.4 環(huán)境要求
| 組件 | 版本要求 | 說(shuō)明 |
|---|---|---|
| 操作系統(tǒng) | CentOS 7/8、Rocky Linux 8/9、Ubuntu 20.04+ | 文中命令以主流生產(chǎn)發(fā)行版為準(zhǔn) |
| 診斷工具 | procps-ng 、sysstat、perf、strace、lsof | sysstat 提供sar/mpstat/pidstat,perf用于熱點(diǎn)采樣 |
| 硬件配置 | 4 vCPU / 8 GB 內(nèi)存起 | 低于該配置時(shí) CPU 抖動(dòng)更明顯,結(jié)論要結(jié)合負(fù)載模型看 |
| 監(jiān)控體系 | Prometheus + Node Exporter 或等價(jià)方案 | 用于回放故障窗口和設(shè)置閾值 |
二、詳細(xì)步驟
2.1 準(zhǔn)備工作
2.1.1 系統(tǒng)檢查
先確認(rèn)這臺(tái)機(jī)器是不是真的在燒 CPU,而不是負(fù)載高、IO 卡頓或虛擬機(jī)被宿主機(jī)搶占。第一輪命令我通常固定成下面這一組:
date hostname -f uptime w top -b -n 1 | head -20 mpstat -P ALL 1 3 vmstat 1 5 sar -u 1 5 sar -q 1 5 free -h df -h dmesg -T | tail -50
這組命令重點(diǎn)看下面幾個(gè)指標(biāo):
%usr:用戶態(tài) CPU,高了通常先看應(yīng)用進(jìn)程或線程熱點(diǎn)
%sys:系統(tǒng)態(tài) CPU,高了先看網(wǎng)絡(luò)、磁盤(pán)、中斷、內(nèi)核路徑
%iowait:高了不一定是 CPU 問(wèn)題,很多人會(huì)誤判
%steal:云主機(jī)上很關(guān)鍵,高了說(shuō)明宿主機(jī)在搶 CPU
run queue:vmstat中的r持續(xù)大于 CPU 核數(shù),說(shuō)明調(diào)度壓力明顯
load average:只說(shuō)明等待隊(duì)列變長(zhǎng),不等于 CPU 一定打滿
如果現(xiàn)場(chǎng)還沒(méi)裝診斷工具,先補(bǔ)齊:
# Debian / Ubuntu sudo apt update sudo apt install -y sysstat linux-tools-common linux-tools-generic strace lsof iotop dstat tcpdump linux-cpupower # RHEL / CentOS / Rocky / AlmaLinux sudo yum install -y sysstat perf strace lsof iotop dstat tcpdump kernel-tools
生產(chǎn)環(huán)境里建議把sysstat常駐打開(kāi),不要等出事了才發(fā)現(xiàn)機(jī)器上沒(méi)有sar歷史數(shù)據(jù)。
2.1.2 固定現(xiàn)場(chǎng)
CPU 問(wèn)題最容易丟現(xiàn)場(chǎng),尤其是應(yīng)用被自動(dòng)拉起、容器自動(dòng)重建或者值班同學(xué)順手重啟服務(wù)。先把關(guān)鍵現(xiàn)場(chǎng)落盤(pán):
sudo mkdir -p /var/log/cpu-hotspot/$(date +%F_%H%M%S)
SNAPSHOT_DIR=$(ls -dt /var/log/cpu-hotspot/* | head -1)
top -b -n 1 >"${SNAPSHOT_DIR}/top.txt"
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head -50 >"${SNAPSHOT_DIR}/ps_top_cpu.txt"
mpstat -P ALL 1 5 >"${SNAPSHOT_DIR}/mpstat.txt"
pidstat -u -r -d -h 1 5 >"${SNAPSHOT_DIR}/pidstat.txt"
vmstat 1 5 >"${SNAPSHOT_DIR}/vmstat.txt"
sar -u 1 5 >"${SNAPSHOT_DIR}/sar_u.txt"
sar -n DEV 1 5 >"${SNAPSHOT_DIR}/sar_net.txt"
sar -d 1 5 >"${SNAPSHOT_DIR}/sar_disk.txt"
dmesg -T | tail -200 >"${SNAPSHOT_DIR}/dmesg_tail.txt"
如果是容器環(huán)境,再補(bǔ)一組 cgroup 和 kubelet 側(cè)信息:
kubectl top pod -A --containers | sort -k3 -hr | head -20 kubectl describe pod-n crictl stats cat /sys/fs/cgroup/cpu.max 2>/dev/null || cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us cat /sys/fs/cgroup/cpu.stat 2>/dev/null || cat /sys/fs/cgroup/cpu/cpu.stat
這里最容易踩坑的是:容器里看到 CPU 100%,但那是**單核 100%**,不是整機(jī) 100%。如果 Pod 只給了500m,應(yīng)用被節(jié)流后一樣會(huì)表現(xiàn)成 RT 飆升。
2.2 核心排查步驟
2.2.1 先判斷是整機(jī)問(wèn)題還是單進(jìn)程問(wèn)題
先看全局,再看個(gè)體:
mpstat -P ALL 1 3 ps -eo pid,user,ni,pri,psr,stat,%cpu,%mem,etime,cmd --sort=-%cpu | head -30 pidstat -u -p ALL 1 5
判斷原則我一般這么定:
所有核心都高,且多個(gè)業(yè)務(wù)進(jìn)程都在跑:優(yōu)先看流量、批處理、宿主機(jī)爭(zhēng)搶、全局中斷
只有 1-2 個(gè)核心高:大概率是單線程熱點(diǎn)、鎖競(jìng)爭(zhēng)、自旋或綁核不均
整機(jī) CPU 不高,單進(jìn)程 CPU 很高:直接進(jìn)線程級(jí)定位
整機(jī)%sys高、進(jìn)程視角又不明顯:轉(zhuǎn)到中斷、網(wǎng)絡(luò)棧、磁盤(pán) IO 路徑
業(yè)務(wù)止血時(shí),不要急著kill -9。先確認(rèn)是不是可以摘流:
# 例如在負(fù)載均衡層先摘掉異常節(jié)點(diǎn) curl -s http://127.0.0.1:9090/healthz sudo ipvsadm -Ln sudo ss -s
如果節(jié)點(diǎn)還能響應(yīng),優(yōu)先在網(wǎng)關(guān)或注冊(cè)中心把實(shí)例摘掉,再做深度取證。
2.2.2 區(qū)分用戶態(tài) CPU 和系統(tǒng)態(tài) CPU
這一刀必須切清楚,不然會(huì)把應(yīng)用問(wèn)題排到內(nèi)核層,或者把中斷風(fēng)暴排到業(yè)務(wù)線程。
top -b -n 1 | head -10 mpstat -P ALL 1 5 sar -u ALL 1 5
經(jīng)驗(yàn)上可以這樣理解:
%usr + %nice高:代碼熱點(diǎn)、死循環(huán)、頻繁 JSON 編解碼、正則、GC、壓縮解壓
%sys高:系統(tǒng)調(diào)用密集、網(wǎng)絡(luò)包處理、軟中斷、磁盤(pán)路徑、連接風(fēng)暴
%soft高:網(wǎng)絡(luò)包小包過(guò)多、連接突發(fā)、iptables/conntrack 壓力
%irq高:硬件中斷、網(wǎng)卡隊(duì)列、某些存儲(chǔ)控制器異常
%steal高:不是你機(jī)器的問(wèn)題,先找云平臺(tái)或宿主機(jī)資源爭(zhēng)搶
如果%sys明顯高,繼續(xù)執(zhí)行:
cat /proc/softirqs cat /proc/interrupts sar -n DEV,EDEV 1 5 ethtool -S eth0 | egrep'drop|miss|error|timeout'
如果%usr高,直接進(jìn)入進(jìn)程和線程熱點(diǎn)定位。
2.2.3 進(jìn)程級(jí)定位:誰(shuí)在吃 CPU
先把 top 進(jìn)程抓出來(lái),再看生命周期和啟動(dòng)參數(shù):
ps -eo pid,ppid,lstart,etime,pcpu,pmem,cmd --sort=-pcpu | head -20 pidstat -u -t -p1 5 cat /proc/ /status cat /proc/ /limits lsof -p | head -50
幾個(gè)實(shí)戰(zhàn)判斷點(diǎn):
新拉起的進(jìn)程 CPU 高:通常和新版本發(fā)布、配置變更、緩存未熱有關(guān)
運(yùn)行幾天后的進(jìn)程 CPU 高:更多見(jiàn)于線程泄漏、連接泄漏、任務(wù)堆積、GC 退化
只有某個(gè)工作線程高:優(yōu)先抓線程棧,不要先抓完整堆 dump,太重
Java 進(jìn)程排查可以直接把高 CPU 線程映射到十六進(jìn)制線程號(hào):
top -H -pprintf'0x%x ' jstack | less
Go 進(jìn)程建議先看pprof:
curl -s http://127.0.0.1:6060/debug/pprof/profile?seconds=30 -o cpu.pprof go tool pprof -top cpu.pprof
Python 進(jìn)程如果 CPU 很高,先看是不是純 Python 死循環(huán)、JSON 序列化過(guò)重,或者多進(jìn)程 worker 數(shù)配大了:
top -H -pstrace -p -tt -T -f -o /tmp/python.strace -c
2.2.4 線程級(jí)定位:哪個(gè)線程、哪段棧在熱
線程級(jí)定位是 CPU 問(wèn)題里最有價(jià)值的一步,很多線上問(wèn)題在這里就能定性。
top -H -pps -Lp -o pid,tid,psr,pcpu,stat,comm --sort=-pcpu | head -20 pidstat -t -p 1 5
常見(jiàn)現(xiàn)象和結(jié)論:
某個(gè)線程固定占滿 100% 單核:典型死循環(huán)、自旋鎖、空輪詢(xún)
多個(gè)線程同時(shí)高:并發(fā)打滿、線程池放大、熱點(diǎn) key 或批任務(wù)沖擊
線程 CPU 高且上下文切換也高:可能是鎖競(jìng)爭(zhēng)、頻繁喚醒、線程數(shù)過(guò)多
繼續(xù)往下抓熱點(diǎn)時(shí),優(yōu)先用perf,比strace更適合 CPU 分析:
sudo perf top -p-g sudo perf record -F 99 -p -g -- sleep 30 sudo perf report --stdio | head -80
perf的使用原則:
采樣 15-30 秒通常夠了,時(shí)間太長(zhǎng)會(huì)把短期熱點(diǎn)沖淡
線上優(yōu)先-F 99或-F 49,不要把采樣頻率拉太高
perf top適合現(xiàn)場(chǎng)判斷,perf record/report適合留證據(jù)
如果內(nèi)核限制了perf_event_paranoid,臨時(shí)調(diào)整前先評(píng)估權(quán)限:
sysctl kernel.perf_event_paranoid sudo sysctl -w kernel.perf_event_paranoid=1
改完記得回收,別把調(diào)試口子常駐留在生產(chǎn)機(jī)上。
2.2.5 系統(tǒng)態(tài) CPU 高:重點(diǎn)盯中斷、網(wǎng)絡(luò)和磁盤(pán)
系統(tǒng)態(tài)高最容易出現(xiàn)“應(yīng)用沒(méi)問(wèn)題,但機(jī)器已經(jīng)快扛不住”的情況。排查順序建議固定:
看網(wǎng)卡收發(fā)和丟包
看軟中斷分布
看連接狀態(tài)和 backlog
看磁盤(pán)隊(duì)列和 IO 等待
看內(nèi)核日志有沒(méi)有網(wǎng)卡、驅(qū)動(dòng)、文件系統(tǒng)異常
sar -n DEV,EDEV,TCP,ETCP 1 5 ss -s ss -ant state syn-recv netstat -s | egrep -i'listen|overflow|drop|retrans' cat /proc/softirqs cat /proc/net/softnet_stat iostat -xz 1 5 pidstat -d 1 5
幾種典型模式:
NET_RX飆高:小包洪峰、負(fù)載均衡不均、網(wǎng)卡隊(duì)列/中斷綁核不合理
ksoftirqd高:軟中斷堆積,用戶態(tài)進(jìn)程看起來(lái)并不高,但 CPU 已經(jīng)被內(nèi)核吃掉
wa高、avgqu-sz高:不是 CPU 問(wèn)題本身,根因在 IO
SYN-RECV很多:上游連接風(fēng)暴、半連接隊(duì)列不足、攻擊流量或健康檢查異常
2.2.6 云主機(jī)、虛擬化和容器環(huán)境的特殊檢查
很多“CPU 高”其實(shí)是資源被限制或者宿主機(jī)搶占。
mpstat -P ALL 1 5 sar -u ALL 1 5 grep . /sys/fs/cgroup/cpu.max 2>/dev/null grep . /sys/fs/cgroup/cpu.stat 2>/dev/null cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us 2>/dev/null cat /sys/fs/cgroup/cpu/cpu.cfs_period_us 2>/dev/null
重點(diǎn)關(guān)注:
%steal持續(xù)超過(guò) 5%:宿主機(jī)資源爭(zhēng)搶?zhuān)瑯I(yè)務(wù)再怎么調(diào)也只是緩解
nr_throttled持續(xù)增長(zhǎng):容器被 CPU quota 節(jié)流
usage_usec增長(zhǎng)平緩但 RT 很高:可能是線程拿不到調(diào)度片
Kubernetes 場(chǎng)景里,再補(bǔ)兩步:
kubectl top node kubectl top pod -A --containers | head -30 kubectl describe node| egrep -A3'Allocated resources|Non-terminated Pods'
如果節(jié)點(diǎn)超賣(mài)嚴(yán)重,優(yōu)先做資源回收、親和性重排、Pod 驅(qū)逐,而不是只盯某個(gè)業(yè)務(wù)進(jìn)程。
2.3 驗(yàn)證排查結(jié)論
2.3.1 先驗(yàn)證臨時(shí)止血?jiǎng)幼魇欠裆?/p>
常見(jiàn)止血?jiǎng)幼饔腥N:摘流、限流、降并發(fā)。動(dòng)作做完后至少看 10 分鐘,不要看 30 秒就下結(jié)論。
watch -n 2"uptime; echo '---'; mpstat -P ALL 1 1; echo '---'; ss -s"
止血?jiǎng)幼饔行r(shí),通常會(huì)看到:
熱點(diǎn)核心利用率下降到 60%-75%
業(yè)務(wù) RT 在 3-5 分鐘內(nèi)回落
連接重傳率和超時(shí)率同步下降
2.3.2 再驗(yàn)證根因是否閉環(huán)
根因驗(yàn)證至少要滿足三件事:
# 1. 熱點(diǎn)線程已消失或熱點(diǎn)函數(shù)占比明顯下降 sudo perf report --stdio | head -40 # 2. 關(guān)鍵業(yè)務(wù)接口恢復(fù) curl -s -o /dev/null -w"%{http_code} %{time_total} "http://127.0.0.1:8080/health # 3. 監(jiān)控指標(biāo)回歸 sar -u 1 3
閉環(huán)標(biāo)準(zhǔn)建議寫(xiě)得硬一點(diǎn):
CPU 峰值恢復(fù)到過(guò)去 7 天同時(shí)間窗 P95 附近
錯(cuò)誤率回到告警閾值以下
相同流量下未再次出現(xiàn)同類(lèi)熱點(diǎn)線程
三、示例代碼和配置
3.1 完整配置示例
3.1.1 主配置文件
下面這份配置用于開(kāi)啟sysstat長(zhǎng)期采樣,保證故障發(fā)生后能回看 CPU、IO、網(wǎng)絡(luò)的歷史走勢(shì)。線上機(jī)器不留歷史指標(biāo),很多 CPU 故障最后只能靠猜。
# 文件路徑:/etc/sysconfig/sysstat HISTORY=28 COMPRESSAFTER=7 SADC_OPTIONS="-S DISK -S XDISK -S INT -S IPV6" SA_DIR=/var/log/sa YESTERDAY=no
CentOS 7/8 系列還要確認(rèn)定時(shí)任務(wù)開(kāi)啟:
# 文件路徑:/usr/lib/systemd/system/sysstat-collect.timer [Unit] Description=Run system activity accounting tool every 10 minutes [Timer] OnCalendar=*:00/10 AccuracySec=1min Persistent=true [Install] WantedBy=timers.target
啟用方式:
sudo systemctl daemon-reload sudo systemctlenable--now sysstat sudo systemctlenable--now sysstat-collect.timer sudo systemctlenable--now sysstat-summary.timer
3.1.2 輔助腳本
這份腳本用來(lái)在 CPU 異常時(shí)自動(dòng)抓取快照,適合掛在定時(shí)任務(wù)、Prometheus Alertmanager webhook 或值班手冊(cè)里。腳本目標(biāo)不是“自動(dòng)修復(fù)”,而是把最容易丟的現(xiàn)場(chǎng)第一時(shí)間保存下來(lái)。
#!/usr/bin/env bash
# 文件名:/usr/local/bin/cpu_hotspot_snapshot.sh
# 功能:采集 CPU 異常現(xiàn)場(chǎng),保留進(jìn)程、線程、網(wǎng)絡(luò)、磁盤(pán)和內(nèi)核證據(jù)
set-euo pipefail
BASE_DIR="/var/log/cpu-hotspot"
TS="$(date +%F_%H%M%S)"
OUT_DIR="${BASE_DIR}/${TS}"
mkdir -p"${OUT_DIR}"
echo"[INFO] snapshot dir:${OUT_DIR}"
{
echo"===== basic ====="
date
hostname -f
uptime
uname -a
echo
echo"===== top ====="
top -b -n 1 | head -40
echo
echo"===== mpstat ====="
mpstat -P ALL 1 3
echo
echo"===== vmstat ====="
vmstat 1 5
echo
echo"===== sar cpu ====="
sar -u ALL 1 3
echo
echo"===== sar net ====="
sar -n DEV,EDEV,TCP,ETCP 1 3
echo
echo"===== iostat ====="
iostat -xz 1 3
} >"${OUT_DIR}/system.txt"2>&1
ps -eo pid,ppid,user,psr,stat,%cpu,%mem,lstart,cmd --sort=-%cpu | head -50
>"${OUT_DIR}/top_processes.txt"
pidstat -u -r -d -t -h 1 5 >"${OUT_DIR}/pidstat.txt"2>&1 ||true
ss -s >"${OUT_DIR}/ss_summary.txt"2>&1 ||true
cat /proc/softirqs >"${OUT_DIR}/softirqs.txt"2>&1 ||true
cat /proc/interrupts >"${OUT_DIR}/interrupts.txt"2>&1 ||true
dmesg -T | tail -200 >"${OUT_DIR}/dmesg_tail.txt"2>&1 ||true
TOP_PID="$(ps -eo pid,%cpu --sort=-%cpu | awk 'NR==2 {print $1}')"
if[[ -n"${TOP_PID}"&&"${TOP_PID}"=~ ^[0-9]+$ ]];then
ps -Lp"${TOP_PID}"-o pid,tid,psr,pcpu,stat,comm --sort=-pcpu
>"${OUT_DIR}/pid_${TOP_PID}_threads.txt"2>&1 ||true
timeout 20 perf record -F 49 -p"${TOP_PID}"-g -- sleep 15
>"${OUT_DIR}/perf_record.log"2>&1 ||true
perf report --stdio >"${OUT_DIR}/perf_report.txt"2>&1 ||true
fi
find"${BASE_DIR}"-maxdepth 1 -typed -mtime +7 -execrm -rf {} ; ||true
echo"[INFO] snapshot completed"
部署建議:
sudo install -o root -g root -m 0750 cpu_hotspot_snapshot.sh /usr/local/bin/cpu_hotspot_snapshot.sh echo'*/5 * * * * root /usr/local/bin/cpu_hotspot_snapshot.sh >/dev/null 2>&1'| sudo tee /etc/cron.d/cpu-hotspot
如果機(jī)器數(shù)量多,別每 5 分鐘全量抓。正確做法是只在告警觸發(fā)時(shí)抓,或者給腳本加條件判斷,例如最近 1 分鐘 CPU 大于 85% 才采樣。
3.2 實(shí)際應(yīng)用案例
案例一:Java 線程死循環(huán)把單核打滿
場(chǎng)景描述:某訂單服務(wù)發(fā)布后 RT 從 30 ms 拉高到 800 ms,監(jiān)控上整機(jī) CPU 只有 45%,但 1 個(gè)核心長(zhǎng)期 100%,業(yè)務(wù)側(cè)以為是數(shù)據(jù)庫(kù)慢,實(shí)際上根因在應(yīng)用線程。
排查過(guò)程:
top -H -p 28461 ps -Lp 28461 -o pid,tid,pcpu,comm --sort=-pcpu | head printf'0x%x '28513 jstack 28461 | grep -A 20 6f61 sudo perf top -p 28461 -g
關(guān)鍵現(xiàn)象:
線程28513持續(xù)占用 99% 單核
jstack顯示線程卡在業(yè)務(wù)規(guī)則引擎的循環(huán)判斷里
perf熱點(diǎn)集中在字符串拆分和正則匹配
實(shí)現(xiàn)代碼:
publicvoidcalculateDiscount(Listrules){ while(true) { for(String rule : rules) { if(rule.matches(".*VIP.*")) { doSomething(rule.split(":")[1]); } } } }
這段代碼在測(cè)試環(huán)境不容易暴露問(wèn)題,因?yàn)橐?guī)則量小、線程少。線上規(guī)則列表擴(kuò)到 3 萬(wàn)條之后,死循環(huán)加正則匹配直接把單核打穿。
修復(fù)動(dòng)作:
先在注冊(cè)中心把異常實(shí)例摘流
回滾到上一版本,確認(rèn) CPU 回落
用緩存和預(yù)編譯規(guī)則替換循環(huán)內(nèi)的matches
給線程執(zhí)行邏輯加退出條件和超時(shí)保護(hù)
運(yùn)行結(jié)果:
修復(fù)前:?jiǎn)魏?99%,實(shí)例 P99 RT 1.2s,訂單超時(shí)率 8.7% 修復(fù)后:熱點(diǎn)線程消失,整機(jī) CPU 下降到 31%,P99 RT 恢復(fù)到 85ms
案例二:軟中斷堆積導(dǎo)致網(wǎng)關(guān)節(jié)點(diǎn)系統(tǒng)態(tài) CPU 飆升
場(chǎng)景描述:某 API 網(wǎng)關(guān)節(jié)點(diǎn)在晚高峰出現(xiàn)%sys70% 以上,Nginx Worker 進(jìn)程 CPU 并不高,但機(jī)器整體不可用,請(qǐng)求連接超時(shí)明顯增多。
排查過(guò)程:
top -b -n 1 | head -10 cat /proc/softirqs | column -t | egrep'NET_RX|NET_TX' sar -n DEV,EDEV,TCP,ETCP 1 5 ss -s ethtool -S eth0 | egrep'drop|miss|queue'
關(guān)鍵現(xiàn)象:
NET_RX軟中斷在 CPU0、CPU1 上遠(yuǎn)高于其他核心
ksoftirqd/0、ksoftirqd/1持續(xù)占用 CPU
網(wǎng)卡多隊(duì)列開(kāi)了,但中斷綁核不均,流量基本都?jí)涸谇皟蓚€(gè)核心
處理腳本:
#!/usr/bin/env bash
# 文件名:rebalance_irq.sh
set-euo pipefail
SERVICE="irqbalance"
NIC="eth0"
sudo systemctlenable--now"${SERVICE}"
sudo ethtool -L"${NIC}"combined 8
forirqin$(grep"${NIC}"/proc/interrupts | awk -F:'{print $1}');do
echo0f | sudo tee"/proc/irq/${irq}/smp_affinity">/dev/null
done
運(yùn)行結(jié)果:
調(diào)整前:sys 72%,NET_RX 在 CPU0/1 明顯傾斜,請(qǐng)求超時(shí)率 5.4% 調(diào)整后:sys 下降到 28%,各核心軟中斷分布趨于均衡,請(qǐng)求超時(shí)率降到 0.3%
這個(gè)案例里,如果只盯業(yè)務(wù)進(jìn)程,基本看不出問(wèn)題。CPU 真正被打滿的是內(nèi)核網(wǎng)絡(luò)收包路徑。
四、最佳實(shí)踐和注意事項(xiàng)
4.1 最佳實(shí)踐
4.1.1 性能優(yōu)化
優(yōu)化點(diǎn)一:常駐歷史采樣,別等出事再裝工具
sar歷史數(shù)據(jù)對(duì) CPU 問(wèn)題非常關(guān)鍵,尤其是那些“現(xiàn)在已經(jīng)恢復(fù),但昨晚 21:10 出過(guò)抖動(dòng)”的故障。生產(chǎn)環(huán)境建議至少保留 28 天數(shù)據(jù)。
sudo sed -i's/^HISTORY=.*/HISTORY=28/'/etc/sysconfig/sysstat sudo systemctlenable--now sysstat sudo systemctl restart sysstat
優(yōu)化點(diǎn)二:給熱點(diǎn)業(yè)務(wù)做合理的線程上限
線程數(shù)不是越大越好。CPU 密集型業(yè)務(wù)線程池開(kāi)太大,只會(huì)把上下文切換和鎖競(jìng)爭(zhēng)一起抬高。我們團(tuán)隊(duì)的經(jīng)驗(yàn)值是:CPU 密集型服務(wù)先按CPU 核數(shù) * 1~2起步,再壓測(cè)修正。
nproc pidstat -w -p1 5
如果cswch/s和nvcswch/s持續(xù)很高,同時(shí) CPU 利用率卻上不去,線程數(shù)大概率已經(jīng)過(guò)量。
優(yōu)化點(diǎn)三:網(wǎng)絡(luò)型節(jié)點(diǎn)優(yōu)先處理軟中斷綁核
網(wǎng)關(guān)、L4/L7 代理、日志采集節(jié)點(diǎn),CPU 打滿很多時(shí)候不是應(yīng)用邏輯,而是收包路徑失衡。實(shí)測(cè)下來(lái),雙 10G 網(wǎng)卡機(jī)器如果中斷集中在 1-2 個(gè)核上,業(yè)務(wù)峰值流量一來(lái)就會(huì)明顯抖。
irqbalance --debug 2>/dev/null | head cat /proc/interrupts sudo systemctlenable--now irqbalance
4.1.2 安全加固
安全措施一:限制調(diào)試工具使用權(quán)限
perf、strace、gdb都是排障利器,但也能帶來(lái)信息泄露風(fēng)險(xiǎn)。生產(chǎn)環(huán)境建議只給運(yùn)維值班組和 SRE 小范圍授權(quán)。
getfacl /usr/bin/perf sudo chmod 750 /usr/bin/perf
安全措施二:對(duì) CPU 調(diào)優(yōu)動(dòng)作做審計(jì)
改sysctl、改 IRQ 綁核、改容器 quota 都要進(jìn)變更記錄。沒(méi)有審計(jì)的“臨時(shí)優(yōu)化”,一個(gè)月后基本沒(méi)人記得動(dòng)過(guò)什么。
sudo auditctl -w /etc/sysctl.conf -p wa -k sysctl_change sudo auditctl -w /etc/security/limits.conf -p wa -k limits_change
安全措施三:對(duì)采樣腳本做最小權(quán)限執(zhí)行
快照腳本盡量只讀,不要順手把“自動(dòng) kill 高 CPU 進(jìn)程”塞進(jìn)去。自動(dòng)止血可以做,但必須經(jīng)過(guò)白名單和二次確認(rèn),否則誤傷概率很高。
4.1.3 高可用配置
HA 方案一:業(yè)務(wù)實(shí)例接入負(fù)載均衡健康檢查,出現(xiàn) CPU 熱點(diǎn)時(shí)支持秒級(jí)摘流
HA 方案二:關(guān)鍵服務(wù)采用多可用區(qū)部署,避免單臺(tái)熱點(diǎn)機(jī)器拖垮整個(gè)業(yè)務(wù)面
備份策略:所有 CPU 調(diào)優(yōu)相關(guān)配置在修改前先做版本化備份,尤其是sysctl、網(wǎng)卡參數(shù)、服務(wù)線程配置
建議把下面這些動(dòng)作標(biāo)準(zhǔn)化:
sudo cp -a /etc/sysctl.conf /etc/sysctl.conf.$(date +%F_%H%M%S).bak sudo sysctl -a > /var/backups/sysctl-all.$(date +%F_%H%M%S).txt tar czf /var/backups/service-config-$(date +%F_%H%M%S).tar.gz /etc/systemd/system /etc/nginx /etc/myapp 2>/dev/null
4.2 注意事項(xiàng)
4.2.1 配置注意事項(xiàng)
警告:CPU 問(wèn)題沒(méi)有確認(rèn)根因前,不要直接重啟、擴(kuò)容或改線程池。動(dòng)作雖然能短時(shí)壓住癥狀,但會(huì)把現(xiàn)場(chǎng)打散,后面再?gòu)?fù)盤(pán)就只能靠猜。
注意事項(xiàng)一:load average高不等于 CPU 高。很多人看到負(fù)載 30 就判定 CPU 打滿,最后發(fā)現(xiàn)是磁盤(pán)卡住
注意事項(xiàng)二:容器 CPU 100% 先換算成限額視角再判斷,500m配額下 100% 和整機(jī) 100% 不是一個(gè)量級(jí)
注意事項(xiàng)三:strace -f -p對(duì)高并發(fā)服務(wù)有侵入,優(yōu)先用perf、pidstat、線程棧做輕量定位
4.2.2 常見(jiàn)錯(cuò)誤
| 錯(cuò)誤現(xiàn)象 | 原因分析 | 解決方案 |
|---|---|---|
| 看到top某進(jìn)程 200% 就以為異常 | 多核環(huán)境下進(jìn)程可以合法使用多個(gè)核 | 結(jié)合核數(shù)和線程數(shù)判斷,查看線程級(jí) CPU 分布 |
| 只看當(dāng)前時(shí)刻,沒(méi)有歷史趨勢(shì) | 瞬時(shí) CPU 回落后,現(xiàn)場(chǎng)已經(jīng)丟失 | 常駐sysstat、保留 Prometheus 歷史數(shù)據(jù)、告警觸發(fā)自動(dòng)快照 |
| 只抓進(jìn)程 CPU,不看%sys/%soft/%steal | 根因可能在內(nèi)核、中斷或虛擬化層 | 先分層判斷,再做針對(duì)性定位 |
4.2.3 兼容性問(wèn)題
版本兼容:perf最好和當(dāng)前運(yùn)行內(nèi)核版本匹配,不匹配時(shí)符號(hào)解析會(huì)不準(zhǔn)
平臺(tái)兼容:在云廠商共享宿主機(jī)上,%steal的參考價(jià)值高于物理機(jī)
組件依賴(lài):容器環(huán)境下查看 cgroup 指標(biāo)時(shí),需要區(qū)分 cgroup v1 和 cgroup v2 的文件路徑
五、故障排查和監(jiān)控
5.1 故障排查
5.1.1 日志查看
CPU 飆高雖然是資源問(wèn)題,但日志仍然能給出觸發(fā)時(shí)間點(diǎn)、請(qǐng)求類(lèi)型和錯(cuò)誤放大路徑。日志至少看三類(lèi):系統(tǒng)日志、應(yīng)用日志、內(nèi)核日志。
# 查看系統(tǒng)日志 sudo journalctl -S -30min # 查看應(yīng)用日志 tail -f /var/log/myapp/app.log # 查看錯(cuò)誤日志 grep -E"ERROR|Timeout|RejectedExecution|GC overhead"/var/log/myapp/app.log | tail -50
如果是 Java 服務(wù),再補(bǔ) GC 日志:
grep -E"Pause Young|Pause Full|Full GC"/var/log/myapp/gc.log | tail -50
5.1.2 常見(jiàn)問(wèn)題排查
問(wèn)題一:CPU 100%,但top看不到明顯高進(jìn)程
top -b -n 1 | head -10 mpstat -P ALL 1 5 cat /proc/softirqs cat /proc/interrupts
癥狀:整機(jī) CPU 高,單個(gè)業(yè)務(wù)進(jìn)程都不突出,%sys/%soft偏高
診斷:多半是軟中斷、網(wǎng)卡隊(duì)列、驅(qū)動(dòng)或內(nèi)核收包路徑問(wèn)題
解決:
檢查NET_RX分布是否傾斜
檢查irqbalance是否正常工作
檢查是否存在小包洪峰、連接風(fēng)暴或異常抓包任務(wù)
問(wèn)題二:應(yīng)用進(jìn)程 CPU 高,但業(yè)務(wù)量并不大
ps -eo pid,pcpu,pmem,cmd --sort=-pcpu | head top -H -psudo perf record -F 99 -p -g -- sleep 20 sudo perf report --stdio | head -60
癥狀:請(qǐng)求量一般,實(shí)例仍持續(xù)吃滿單核或多核
診斷:常見(jiàn)于代碼死循環(huán)、熱點(diǎn) key、異常重試、自旋鎖、正則或 JSON 開(kāi)銷(xiāo)過(guò)大
解決:
先摘流,保證實(shí)例不繼續(xù)放大影響
抓熱點(diǎn)線程和調(diào)用棧
回滾版本或關(guān)閉問(wèn)題開(kāi)關(guān)
補(bǔ)壓測(cè)用例,避免同類(lèi)邏輯再次進(jìn)生產(chǎn)
問(wèn)題三:CPU 高伴隨 RT 高,%steal明顯抬升
癥狀:業(yè)務(wù) RT 波動(dòng)明顯,整機(jī) CPU 看起來(lái)不算離譜,但%steal持續(xù)超過(guò) 5%
排查:mpstat -P ALL 1 5、云平臺(tái)宿主機(jī)事件、節(jié)點(diǎn)資源爭(zhēng)搶記錄
解決:遷移實(shí)例、切換獨(dú)享型規(guī)格、和云平臺(tái)確認(rèn)宿主機(jī)抖動(dòng)
5.1.3 調(diào)試模式
線上調(diào)試優(yōu)先輕量模式,不要一上來(lái)抓全量 heap dump 或gcore。
# 實(shí)時(shí)查看熱點(diǎn)函數(shù) sudo perf top -p-g # 20 秒采樣 sudo perf record -F 49 -p -g -- sleep 20 # 查看調(diào)試信息 sudo perf report --stdio | head -80
如果是 Java 服務(wù),補(bǔ)一個(gè)只讀線程棧:
jstack -l> /tmp/jstack.$(date +%s).log
5.2 性能監(jiān)控
5.2.1 關(guān)鍵指標(biāo)監(jiān)控
# CPU使用率 mpstat -P ALL 1 3 # 進(jìn)程維度 CPU pidstat -u -p ALL 1 3 # 網(wǎng)絡(luò)連接 ss -s # 磁盤(pán)IO iostat -xz 1 3
建議重點(diǎn)盯這些指標(biāo):
node_cpu_seconds_total按 mode 拆分后的user/system/iowait/steal/softirq
單機(jī) 1 分鐘和 5 分鐘load average
進(jìn)程級(jí) CPU Top N
網(wǎng)絡(luò)重傳、丟包、SYN backlog overflow
容器throttled_periods_total
5.2.2 監(jiān)控指標(biāo)說(shuō)明
| 指標(biāo)名稱(chēng) | 正常范圍 | 告警閾值 | 說(shuō)明 |
|---|---|---|---|
| 整機(jī) CPU 使用率 | 日常峰值低于 70% | 連續(xù) 5 分鐘高于 85% | 先看用戶態(tài)還是系統(tǒng)態(tài) |
| 單核心 CPU 使用率 | 波峰可到 80% | 連續(xù) 3 分鐘高于 95% | 單線程熱點(diǎn)更依賴(lài)這個(gè)指標(biāo) |
| steal 比例 | 低于 1% | 連續(xù) 3 分鐘高于 5% | 云主機(jī)資源爭(zhēng)搶的典型信號(hào) |
| softirq 比例 | 低于 10% | 連續(xù) 3 分鐘高于 25% | 網(wǎng)卡中斷、連接洪峰常見(jiàn) |
| 進(jìn)程 CPU Top1 | 隨業(yè)務(wù)波動(dòng) | 單進(jìn)程長(zhǎng)期高于 200% | 多核進(jìn)程需結(jié)合線程視角判斷 |
| 容器節(jié)流次數(shù) | 接近 0 | 5 分鐘內(nèi)持續(xù)增長(zhǎng) | 說(shuō)明 CPU quota 不夠 |
5.2.3 監(jiān)控告警配置
# 文件路徑:prometheus/rules/cpu_hotspot.yml
groups:
-name:cpu-hotspot
rules:
-alert:HostCpuHigh
expr:100-(avgby(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100)>85
for:5m
labels:
severity:critical
annotations:
summary:"主機(jī) CPU 持續(xù)過(guò)高"
description:"{{ $labels.instance }}CPU 連續(xù) 5 分鐘高于 85%"
-alert:HostSoftIrqHigh
expr:avgby(instance)(rate(node_cpu_seconds_total{mode="softirq"}[5m]))*100>25
for:3m
labels:
severity:warning
annotations:
summary:"主機(jī)軟中斷占比過(guò)高"
description:"{{ $labels.instance }}軟中斷 CPU 持續(xù)高于 25%"
-alert:ContainerCpuThrottlingHigh
expr:sumby(pod,namespace)(rate(container_cpu_cfs_throttled_periods_total[5m]))>20
for:5m
labels:
severity:warning
annotations:
summary:"容器 CPU 節(jié)流明顯"
description:"{{ $labels.namespace }}/{{ $labels.pod }}近 5 分鐘 CPU throttling 持續(xù)升高"
5.3 備份與恢復(fù)
5.3.1 備份策略
任何 CPU 調(diào)優(yōu)動(dòng)作之前,先備份配置。尤其是sysctl、IRQ 綁核、服務(wù)線程池、JVM 參數(shù),改錯(cuò)了很容易引發(fā)更大的抖動(dòng)。
#!/usr/bin/env bash
# 文件名:/usr/local/bin/backup_cpu_related_configs.sh
set-euo pipefail
BACKUP_DIR="/var/backups/cpu-tuning/$(date +%F_%H%M%S)"
mkdir -p"${BACKUP_DIR}"
cp -a /etc/sysctl.conf"${BACKUP_DIR}/"
cp -a /etc/sysctl.d"${BACKUP_DIR}/"2>/dev/null ||true
cp -a /etc/security/limits.conf"${BACKUP_DIR}/"2>/dev/null ||true
cp -a /etc/systemd/system"${BACKUP_DIR}/systemd"2>/dev/null ||true
sysctl -a >"${BACKUP_DIR}/sysctl-all.txt"
cat /proc/interrupts >"${BACKUP_DIR}/interrupts.txt"
cat /proc/softirqs >"${BACKUP_DIR}/softirqs.txt"
tar czf"${BACKUP_DIR}.tar.gz"-C"$(dirname "${BACKUP_DIR}")""$(basename "${BACKUP_DIR}")"
echo"backup saved to${BACKUP_DIR}.tar.gz"
5.3.2 恢復(fù)流程
停止臨時(shí)調(diào)優(yōu)動(dòng)作:回滾腳本、關(guān)閉異常定時(shí)任務(wù)、撤銷(xiāo)臨時(shí)限流或綁核
恢復(fù)配置文件:從最近一次備份還原sysctl、服務(wù)配置、線程池參數(shù)
驗(yàn)證完整性:執(zhí)行sysctl --system,檢查服務(wù)配置語(yǔ)法和啟動(dòng)狀態(tài)
重啟或熱加載服務(wù):在低峰期執(zhí)行,并用監(jiān)控觀察至少 10 分鐘
六、總結(jié)
6.1 技術(shù)要點(diǎn)回顧
先分層,再深入:整機(jī)、進(jìn)程、線程、調(diào)用棧四層順著走,判斷不會(huì)跑偏
先分態(tài),再取證:先區(qū)分%usr/%sys/%soft/%steal,再?zèng)Q定抓進(jìn)程還是抓內(nèi)核路徑
先保現(xiàn)場(chǎng),再做動(dòng)作:快照比重啟更重要,沒(méi)有現(xiàn)場(chǎng)很難閉環(huán)
線程級(jí)定位價(jià)值最高:很多 CPU 問(wèn)題真正的根因都藏在熱點(diǎn)線程和調(diào)用棧里
系統(tǒng)態(tài) CPU 不能只盯應(yīng)用:軟中斷、網(wǎng)卡隊(duì)列、連接風(fēng)暴經(jīng)常才是真正的根因
容器環(huán)境要看節(jié)流和 steal:CPU 不夠用不一定是程序?qū)懖?,也可能是配額和宿主機(jī)資源爭(zhēng)搶
6.2 進(jìn)階學(xué)習(xí)方向
深入 Linux 調(diào)度器和 CFS 配額機(jī)制
學(xué)習(xí)資源:Linux kernel documentation、sched相關(guān)內(nèi)核文檔
實(shí)踐建議:在測(cè)試環(huán)境復(fù)現(xiàn) CPU quota 節(jié)流、綁核和調(diào)度延遲問(wèn)題
掌握perf、eBPF、bcc工具鏈
學(xué)習(xí)資源:Brendan Gregg 的性能分析資料、bcc-tools
實(shí)踐建議:先從perf top、perf record、runqlat、profile這類(lèi)輕量工具入手
建立故障自動(dòng)取證體系
學(xué)習(xí)資源:Prometheus Alertmanager webhook、企業(yè)內(nèi)巡檢平臺(tái)
實(shí)踐建議:把 CPU、高負(fù)載、連接風(fēng)暴的快照采集做成統(tǒng)一腳本和 SOP
6.3 參考資料
Linux kernel CPU documentation- Linux 內(nèi)核 CPU、調(diào)度與性能分析文檔
sysstat official site-sar、mpstat、pidstat工具說(shuō)明
perf wiki-perf使用方法和常見(jiàn)問(wèn)題
Brendan Gregg Blog- Linux 性能分析實(shí)戰(zhàn)材料
附錄
A. 命令速查表
top -H -p# 查看進(jìn)程內(nèi)線程 CPU pidstat -u -t -p 1 5 # 觀察線程級(jí) CPU 趨勢(shì) mpstat -P ALL 1 3 # 查看每個(gè)核心使用率 sar -u ALL 1 5 # 查看 CPU 各 mode 分布 sar -n DEV,EDEV,TCP,ETCP 1 5 # 查看網(wǎng)絡(luò)與 TCP 指標(biāo) iostat -xz 1 5 # 查看磁盤(pán)隊(duì)列與 IO 延遲 cat /proc/softirqs # 查看軟中斷分布 cat /proc/interrupts # 查看硬中斷分布 perf top -p -g # 實(shí)時(shí)看熱點(diǎn)函數(shù) perf record -F 99 -p -g -- sleep 20 # 采樣留證據(jù)
B. 配置參數(shù)詳解
kernel.perf_event_paranoid
作用:控制普通用戶使用perf的權(quán)限
建議:生產(chǎn)環(huán)境默認(rèn)保持嚴(yán)格,排障時(shí)臨時(shí)下調(diào),結(jié)束后恢復(fù)
HISTORY
作用:控制sysstat歷史數(shù)據(jù)保留天數(shù)
建議:不少于 28 天,雙十一、618 這類(lèi)業(yè)務(wù)建議保留 60 天
cpu.max/cpu.cfs_quota_us
作用:容器 CPU 配額限制
建議:對(duì)低延遲業(yè)務(wù)慎用過(guò)小配額,避免頻繁 throttling
smp_affinity
作用:控制中斷可在哪些 CPU 上處理
建議:高流量網(wǎng)關(guān)節(jié)點(diǎn)關(guān)注這個(gè)參數(shù),避免中斷只壓在少數(shù)核心
C. 術(shù)語(yǔ)表
| 術(shù)語(yǔ) | 英文 | 解釋 |
|---|---|---|
| 用戶態(tài) | user mode | CPU 在應(yīng)用代碼上消耗的時(shí)間 |
| 系統(tǒng)態(tài) | system mode | CPU 在內(nèi)核代碼、系統(tǒng)調(diào)用、中斷處理上消耗的時(shí)間 |
| 軟中斷 | softirq | Linux 內(nèi)核為網(wǎng)絡(luò)、塊設(shè)備等延后處理設(shè)計(jì)的輕量機(jī)制 |
| 節(jié)流 | throttling | 容器因 CPU quota 不足被強(qiáng)制限制執(zhí)行 |
| 竊取時(shí)間 | steal time | 虛擬機(jī)本該獲得 CPU,但被宿主機(jī)拿去服務(wù)其他虛機(jī)的時(shí)間 |
-
cpu
+關(guān)注
關(guān)注
68文章
11287瀏覽量
225166 -
Linux
+關(guān)注
關(guān)注
88文章
11771瀏覽量
219110 -
服務(wù)器
+關(guān)注
關(guān)注
14文章
10270瀏覽量
91537
原文標(biāo)題:Linux 服務(wù)器 CPU 飆高怎么排查?一套實(shí)戰(zhàn)定位思路講清楚
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
Linux系統(tǒng)CPU占用率100%的排查思路
linux服務(wù)器和windows服務(wù)器
教你linux搭建web服務(wù)器
排查Linux服務(wù)器性能問(wèn)題工具
JVM CPU使用率飆高問(wèn)題的排查分析過(guò)程
如何使用Checkmk監(jiān)控Linux服務(wù)器?
Linux服務(wù)器常見(jiàn)的網(wǎng)絡(luò)故障排查方法
linux查看服務(wù)器配置
服務(wù)器cpu和普通電腦cpu的區(qū)別
Linux服務(wù)器CPU飆升的原因
Linux服務(wù)器CPU飆高怎么排查
評(píng)論