文章目錄
- Pre
- 引言
- 1. CPU 性能瓶頸
- 1.1 top 命令 —— 多維度 CPU 使用率指標
- 1.2 負載(load)——任務排隊情況
- 1.3 vmstat 命令 —— CPU 繁忙與等待
- 2. 內存性能瓶頸
- 2.1 操作系統層面的內存分布
- 2.2 top 命令 —— VIRT / RES / SHR 三個關鍵列
- 2.3 CPU 緩存與偽共享
- 2.4 HugePage 技術
- 2.5 預先加載(AlwaysPreTouch)
- 3. I/O 性能瓶頸
- 3.1 硬盤讀寫性能差異
- “磁盤的速度這么慢,為什么 Kafka 操作磁盤,吞吐量還能那么高?”
- 3.2 top/vmstat 中的 wa 指標
- 3.3 iostat 命令 —— 磁盤 I/O 細節指標
- 3.4 零拷貝原理與實踐
- 傳統流程(無零拷貝)
- 零拷貝(以 sendfile 為例)
- 小結
Pre
性能優化 - 理論篇:常見指標及切入點
性能優化 - 理論篇:性能優化的七類技術手段
- 引言:木桶理論及短板概念,以及為何關注 CPU、內存、I/O 三大組件。
- CPU 性能瓶頸
2.1 top 命令——多維度 CPU 使用率指標介紹;
2.2 負載(load)——任務排隊與多核計算機下的正確理解;
2.3 vmstat 命令——Uninterruptible Sleep、交換分區及上下文切換指標。 - 內存性能瓶頸
3.1 操作系統層面的內存分布(物理內存、虛擬內存、共享內存、邏輯內存、Swap);
3.2 top 命令——VIRT/RES/SHR 三個關鍵列;
3.3 CPU 緩存與偽共享——多級緩存結構、Cache line、@sun.misc.Contended;
3.4 HugePage 技術——TLB、頁表與大頁優勢;
3.5 預先加載(AlwaysPreTouch)——JVM 堆內存預分配。 - I/O 性能瓶頸
4.1 硬盤讀寫性能差異說明(順序寫 vs 隨機寫與 CPU/內存對比);
4.2 top/vmstat 中 wa 指標;
4.3 iostat 命令——%util、avgqu-sz、await、svctm 的含義與閾值;
4.4 零拷貝原理與實踐——傳統拷貝流程 vs sendfile 零拷貝流程。
引言
在性能優化 - 理論篇:性能優化的七類技術手段 中,我們已經簡要介紹了解決性能問題的常見切入點,如算法優化、緩存策略、并發模型等。但是,在實際運維與性能調優過程中,往往需要首先判斷“系統的短板在哪兒”,才能更有針對性地展開優化工作。正如木桶理論所強調的,整體系統的性能取決于最薄弱的那一塊。當 CPU、內存、I/O 這三大計算機資源之間存在速度差異時,就會產生性能瓶頸,拖累整個系統。
目標:
- 哪些系統組件容易成為性能瓶頸;
- 如何通過常用命令和指標判斷它們是否真的已經成為瓶頸。
我們接下來將以 Linux 下常見的 top、vmstat、iostat 等工具為切入口,分別從 CPU、內存和 I/O 三個維度進行,快速鎖定系統可疑短板,為后續深入分析提供方向。
1. CPU 性能瓶頸
CPU(中央處理器)是系統中最核心的計算單元,當 CPU 無法及時處理任務時,就會導致其他任務排隊等待,進而產生明顯的性能問題。下面介紹三種常用命令和思路,用于判斷 CPU 是否出現瓶頸。
1.1 top 命令 —— 多維度 CPU 使用率指標
啟動 top
(或 htop
)后,可以按下數字鍵 1
來查看每個邏輯核心的使用情況。
典型輸出中,我們重點關注以下幾列指標:
- us(user):用戶態占用的 CPU 百分比,即由普通應用程序耗費的 CPU 時間;
- sy(system):內核態占用的 CPU 百分比,用于判斷系統調用、驅動中斷等是否頻繁;
- ni(nice):修改過優先級的進程占用的 CPU 百分比,通常比較少見;
- wa(iowait):CPU 因等待 I/O 完成而空閑的時間百分比,當該值過高時,往往意味著 I/O 子系統可能成為瓶頸;
- hi(hardware interrupts):硬中斷所占用的 CPU 百分比,用來評估中斷處理對系統的開銷;
- si(soft interrupts):軟中斷所占用的 CPU 百分比;
- st(steal time):在虛擬化環境中,虛擬機因等待宿主機 CPU 而被“偷取”的時間百分比,常出現在超賣(overcommit)嚴重的云服務器上;
- id(idle):空閑 CPU 百分比,即未被任何用戶態或內核態程序占用的時間。
關注要點:通常,當
id
(空閑)低于 10% 時,就有必要進一步分析。
- 如果
us
?sy
,說明大多數開銷來自應用程序本身的計算;- 如果
sy
較高,且伴隨cs
(上下文切換)急劇上升,則可能是鎖競爭或系統調用過于頻繁;- 如果
wa
持續超過 10%以上,需重點檢查磁盤或網絡 I/O。
1.2 負載(load)——任務排隊情況
除了 top
,uptime
、cat /proc/loadavg
也能查看系統負載(load average),通常顯示最近 1 分鐘、5 分鐘、15 分鐘的平均值。Load 本質上表示系統可運行隊列(包括正在運行與可運行狀態)的長度。
-
單核 CPU:
- Load < 1:CPU 有閑置;
- Load ≈ 1:CPU 滿負載;
- Load > 1:存在任務排隊,開始出現瓶頸。
-
多核 CPU:
我們需要把 Load 與 CPU 核心數進行對比,比如一臺 8 核機器,如果 Load ≈ 8,表明所有核心基本都在滿負載;如果 Load ≈ 16,則代表有 8 個任務在排隊等待。
正確理解:
- 若某時刻 Load = 10,而你使用的是一臺 16 核服務器,說明系統還有剩余計算能力(仍可并行處理多達 6 個任務)。
- 若 Load ≈ CPU 核心數 × 1.2 時,已有一定排隊壓力;若 Load ≈ CPU 核心數 × 1.5 或更高,則問題嚴重。
通過 Load 我們能夠大致判斷“CPU 是否不堪重負”,但無法區分是 CPU 本身飽和,還是等待 I/O 導致的大量進程處于可運行隊列。
1.3 vmstat 命令 —— CPU 繁忙與等待
vmstat 2
(每 2 秒刷新一次)能輸出更細粒度的系統指標,其中幾列指標與 CPU 相關:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----r b swpd free buff cache si so bi bo in cs us sy id wa st2 1 0 10240 40844 124536 0 0 3 12 185 210 30 5 60 5 0
- b(blocked):處于“不可中斷睡眠(D 狀態)”的進程數,通常是因 I/O 等待。如果
b
值持續大于 0,表明存在 I/O 隊列或其他資源等待,可能是 I/O 瓶頸。它的意思是等待 I/O,可能是讀盤或者寫盤動作比較多. - si(swap in)/ so(swap out):每秒發生的換入換出頁數;如果這兩列不為 0,說明系統正在頻繁地使用 Swap,這會嚴重影響性能,應盡快檢查內存壓力。
- cs(context switches):每秒發生的上下文切換次數;當
cs
過高,且同時伴隨sy
較高時,說明進程/線程切換開銷大,可能是線程數過多或鎖競爭激烈。 - us/sy/id/wa:與
top
中類似,判斷用戶/內核占用、空閑與 I/O 等待情況。
綜合判斷:
- 當
b
持續上升、wa
較高時,初步判斷可能是 I/O 瓶頸,需要結合iostat
等工具深入排查;- 當
si
/so
不為 0,說明系統在用 Swap 分區,需檢查內存是否不足;- 當
cs
過高且sy
上升,需分析是否存在鎖競爭或短生命周期進程過多。
此外,可以通過查看單個進程的上下文切換次數來判斷某個進程是否“頻繁切換”,例如:
cat /proc/<PID>/status | grep ctxt_switches
voluntary_ctxt_switches: 93950
nonvoluntary_ctxt_switches: 171204
- voluntary_ctxt_switches:該進程主動自愿讓出 CPU(如等待某個事件完成);
- nonvoluntary_ctxt_switches:該進程被操作系統搶占而發生的上下文切換次數。
如果某進程這兩個值都非常高,說明它在不斷被搶占或頻繁等待,可能是線程數過多或鎖等待。
2. 內存性能瓶頸
內存層面容易出現的問題,主要分為“物理內存不足導x致 Swap 大量使用”與“CPU 緩存相關的并發偽共享”兩種大類。下面我們逐項介紹如何通過系統工具進行排查。
2.1 操作系統層面的內存分布
- 物理內存(RAM):實際安裝在主板上的內存條容量,例如 4GB、8GB 等。
- 虛擬內存(Swap):將磁盤劃分一部分用作內存,當物理內存不足時,操作系統會將部分冷數據遷移到 Swap 以騰出空間。虛擬內存容量 = 物理內存 + Swap 分區。
- 共享內存(Shared Memo ry):多個進程可以映射同一段物理內存,用于進程間高速通信。例如,
/dev/shm
、某些動態鏈接庫(.so 文件)加載到內存后可被多個進程復用。 - 邏輯內存(Virtual Address):每個進程看到的“線性地址空間”,操作系統通過頁表將其映射到實際的物理內存或 Swap。這也是為什么你在程序中看到的指針地址并非物理地址的原因。
注意:當物理內存 + Swap 均被占滿時,新的內存請求會導致 OOM(OutOfMemory)或進程被殺死。
2.2 top 命令 —— VIRT / RES / SHR 三個關鍵列
在 top
中查看進程時,內存相關列通常有:
- VIRT(Virtual Memory Size):進程使用的虛擬內存大小,包括代碼段、庫、堆、棧以及已映射但未實際占用物理內存的區域。通常很大,但并不代表真正占用物理內存。
- RES(Resident Memory Size):進程當前實際占用的物理內存大小,也是最需要關注的數值。如果某個 Java 進程的 RES 一直飆高,說明物理內存壓力較大。
- SHR(Shared Memory Size):進程占用的可被其他進程共享的內存大小,例如共享庫部分。如果多進程復用同一 .so 文件,這部分會重復顯示在各個進程的 SHR 中。
監控要點:
- 持續監測關鍵服務(如 JVM 進程、數據庫進程)的 RES 值。
- 若系統物理內存使用已接近
phy_total – (buffer + cache)
,并且 Swap 使用量不斷增長,則要警惕可能發生的內存抖動與性能下降。
2.3 CPU 緩存與偽共享
CPU 與主內存(DRAM)之間的速度差通常超過百倍,因而多級緩存(L1、L2、L3)成為不可或缺的高速存儲層次:
- L1 Cache:通常 32KB 左右(分為數據緩存與指令緩存),訪問延遲約 4 ~ 5 周期;
- L2 Cache:通常 256KB ~ 512KB,訪問延遲約 12 ~ 15 周期;
- L3 Cache(若存在):幾 MB 級別,延遲約 30 ~ 40 周期;
偽共享(False Sharing) 是并發編程中常見的性能陷阱:
- 當多個線程頻繁修改位于同一個 Cache line(通常 64 字節)內的不同變量時,每次寫操作會導致該整行緩存失效,并在其他 CPU 核之間反復淘汰與重載,極大增加內存系統開銷。
- 舉例:假設兩個線程分別修改位于同一 64 字節 Cache line 內的
a
、b
兩個變量。線程 A 寫a
時,整個 Cache line 被標記為 Modified,線程 B 如果此后寫b
,則需要從主內存或其他 CPU 的緩存中重新加載該 Cache line,才可再寫;如此反復執行,會導致大量緩存一致性流量,嚴重拖慢性能。
要獲得 Cache line 大小,可以執行:
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
cat /sys/devices/system/cpu/cpu0/cache/index1/coherency_line_size
cat /sys/devices/system/cpu/cpu0/cache/index2/coherency_line_size
cat /sys/devices/system/cpu/cpu0/cache/index3/coherency_line_sizecat /proc/cpuinfo | grep cache_alignment
通常返回 64 表示 64 字節的 Cache line 大小。
在 Java 8+ 中,可以通過在類字段上添加 @sun.misc.Contended
注解(并在啟動參數中加上 -XX:-RestrictContended
)來避免某些字段被放在同一 Cache line 中,從而緩解偽共享。但要謹慎使用,因為開啟 @Contended
會導致對象頭占用增加。更常見的做法是:
- 手動在頻繁并發寫的變量之間插入“填充字段”(padding),確保它們位于不同 Cache 行;
- 盡可能使用無鎖/弱同步的數據結構,如
LongAdder
、ConcurrentHashMap
等,減少多個線程對同一變量的寫沖突。
2.4 HugePage 技術
當系統物理內存較大時,傳統 4KB 大小的頁表(Page)在 TLB(Translation Lookaside Buffer)中能映射的頁數量有限,頻繁發生 TLB miss 會帶來高昂的頁表查找開銷。HugePage(大頁)通過將單頁大小從 4KB 改為 2MB(或甚至 1GB,在某些架構上),可以顯著減少頁表條目數量與 TLB miss 的幾率。
-
TLB:CPU 內部的一個小型緩存,用來保存最近訪問的虛擬地址與物理地址映射,當訪問的頁不在 TLB 中時,會發生 TLB miss,引發多次內存訪問以查找頁表。
-
使用 HugePage 的好處:
- 減少頁表條目數量,從而提升 TLB 命中率;
- 減少內核在處理 TLB 缺失時的訪問開銷;
- 對于大內存數據庫、高并發 Java 堆環境(如需要分配 幾十 GB 堆),HugePage 能帶來更穩定的訪問延遲。
-
配置方式(Linux 示例):
-
編輯
/etc/sysctl.conf
,例如:vm.nr_hugepages = 1024 # 預留 1024 個 2MB 大頁(約 2GB)
-
重新加載:
sysctl -p
,并在 JVM 啟動時添加-XX:+UseLargePages
或-XX:UseTransparentHugePages
(內核支持自動管理)。
-
-
注意:HugePage 在申請時需要連續物理內存,如果系統已經運行很久并且內存較為分散,可能會申請失敗;常見做法是在系統啟動時一并保留大頁。
2.5 預先加載(AlwaysPreTouch)
默認情況下,JVM 在啟動過程中會根據 -Xms
(初始堆)和 -Xmx
(最大堆)設置為堆預留地址空間,但實際物理內存分配僅在真正訪問某個頁面時才發生(所謂“按需分配”)。
-
添加參數
-XX:+AlwaysPreTouch
后,JVM 會在啟動時預先觸碰(touch)堆內存中的所有頁面,使操作系統一次性分配所有物理頁面。 -
優點:
- 在后續運行時能保證頁面映射已經就緒,減少首次訪問時因頁面分配帶來的延遲;
- 對于實時性要求較高的服務,可以避免運行過程中出現大規模“頁面分配阻塞”。
-
缺點:啟動時間會明顯變長,尤其是堆較大時需觸摸的頁更多;而且如果常駐內存要占用大量物理頁,系統需要保證提前就有足夠空閑內存。
3. I/O 性能瓶頸
I/O 子系統通常是整臺機器中最慢的部分,因此一旦 I/O 無法跟上 CPU 和內存的處理速度,就會導致 iowait
升高、響應延遲拉長。下面介紹如何使用常用工具判斷與排查 I/O 瓶頸。
3.1 硬盤讀寫性能差異
首先,了解不同存儲介質的讀寫特性:
- 機械硬盤(HDD):順序讀寫性能較好(幾十 MB/s 到上百 MB/s),但隨機讀寫性能極差(可能只有幾 MB/s)。
- SSD(固態硬盤):順序讀寫與隨機讀寫性能都較高,但相對機械盤而言,順序寫入略遜一籌;而隨機小文件 I/O 性能可達數十萬 IOPS。
- 內存(RAM):讀寫帶寬可達數十 GB/s,延遲僅幾十納秒。
由于 CPU 與內存之間的速度差已經很大(百倍以上),因此存儲層與內存層之間的速度差則更為懸殊:
CPU 緩存(L1):幾十 GB/s,延遲 ~4 周期
內存(DRAM):幾十 GB/s,延遲 ~100 納秒
SSD 順序寫:500 MB/s ~ 1 GB/s,延遲數十 微秒
SSD 隨機寫:1 ~ 10 MB/s (取決于塊大小),延遲 ~100 微秒 ~ 1 毫秒
機械盤隨機寫:1 ~ 5 MB/s,延遲 >1 毫秒
因此,在某些高并發場景下,尤其要盡量減少隨機 I/O、避免小文件頻繁讀寫。
“磁盤的速度這么慢,為什么 Kafka 操作磁盤,吞吐量還能那么高?”
磁盤之所以“慢”,主要瓶頸在“尋道”(seek time)操作上。根據 Kafka 官方測試,機械硬盤的尋道時長平均可達到 10ms 左右。與此同時,磁盤對順序寫入與隨機寫入的性能差距極大——順序寫的吞吐往往是隨機寫的數千倍。Kafka 恰好將寫日志(append log)設計為順序寫:
- 每條消息追加到分區對應的日志文件末尾,磁頭僅需在當前寫入位置連續移動,不要頻繁跳轉;
- 借助操作系統頁緩存(PageCache)和批量刷盤(batch flush)策略,將多條消息合并為一次大塊寫入,進一步降低尋道帶來的延遲;
- 通過零拷貝(sendfile)技術,將磁盤頁緩存直接傳輸給網絡套接字,減少內核與用戶空間之間的內存拷貝。
因此,雖然機械磁盤的“隨機寫”極慢,但 Kafka 最大化地利用了“順序寫”的高吞吐特性,進而在每秒百萬級消息寫入場景下依然能夠保持很高的磁盤利用率與整體吞吐。
3.2 top/vmstat 中的 wa 指標
在 top
中,wa
(iowait)列顯示 CPU 因等待 I/O 完成而空閑的百分比;在 vmstat
輸出中同樣會顯示 wa
。
- 如果
wa
持續超過 10%,意味著有大量進程在等待 I/O 完成,此時系統整體吞吐將明顯下降。 - 當
b
(Uninterruptible Sleep) 大于 0 時,也表明某些進程在等待 I/O。
案例:某 Web 服務在高并發下突然響應變慢,
top
發現id
幾乎為 0,wa
常駐 20% 左右,說明大量請求在等待磁盤寫日志或數據庫 IO,可結合iostat
查明是日志盤還是數據庫盤出現瓶頸。
3.3 iostat 命令 —— 磁盤 I/O 細節指標
iostat -x 2
(每 2 秒刷新一次擴展統計)能夠展示各塊設備(Device)的詳細 I/O 性能指標,例如:
Device: rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.00 20.00 0.50 10.00 0.02 0.80 160.00 5.20 30.40 10.20 35.80 2.80 28.00
sdb 0.00 0.00 0.00 0.10 0.00 0.01 80.00 0.02 20.00 0.00 20.00 2.00 0.20
重點關注以下幾列:
- %util:設備利用率,表示磁盤忙碌時間占比。一般當
%util
超過 80% 時,就說明該塊設備壓力過大。 - avgqu-sz(Average Queue Size):平均請求隊列長度,類似“路口排隊汽車數”。數值越小越好;若大于 5 左右,就要警惕排隊嚴重。
- await(Average Wait Time):平均請求等待時長(ms),包括排隊時間與服務時間,經驗上若
await
> 5 ms,就說明磁盤響應變慢;若await
> 10 ms,說明磁盤 I/O 瓶頸明顯。 - svctm(Service Time):平均服務時間(ms),只包含處理時間(不含排隊)。當
await
與svctm
差距很大時,說明排隊時間占比大;當兩者接近時,說明磁盤本身速度接近瓶頸。
閾值參考:
%util
> 80%:磁盤帶寬已接近極限;avgqu-sz
> 5 :隊列較長,建議分散 I/O 或擴容;await
> 10 ms:I/O 響應明顯變慢。
3.4 零拷貝原理與實踐
在傳統的數據從磁盤寫入網絡的流程中,需要 CPU 較多參與:
- read():將磁盤數據從內核空間拷貝到用戶空間;
- write():將用戶空間的數據再次拷貝回內核空間的網絡緩沖區;
- 網絡發送:內核將網絡緩沖區數據通過網卡發出。
中間的兩次拷貝(磁盤→內核→用戶、用戶→內核→網卡)都要經過內存拷貝,且需要在用戶態與內核態之間頻繁切換,增加了 CPU 與內存總線負擔。
傳統流程(無零拷貝)
磁盤 → [內核頁緩存] → <拷貝> → [用戶緩沖區] → <拷貝> → [網絡緩沖區] → 網絡發送
零拷貝(以 sendfile 為例)
磁盤 → [內核頁緩存] ——sendfile——> [網絡緩沖區] → 網絡發送
- 減少一次內存拷貝:sendfile 系統調用讓內核直接將頁緩存映射到網絡緩沖區,省去磁盤→用戶空間→內核網絡緩存的拷貝。
- 減少用戶態/內核態切換:應用直接調用
sendfile(fd_in, fd_out, ...)
,內核內部完成整條鏈路數據流動,無需在中間將數據暴露到用戶空間。
應用場景:
- Kafka:批量順序寫入磁盤并直接 sendfile 將日志 segment 切片零拷貝到 socket;
- Nginx:靜態文件服務時,使用 sendfile 減少 CPU 拷貝開銷;
- Netty:在 Linux 平臺可結合
FileRegion
與sendfile
實現高效大文件傳輸。
并非所有場景都適合零拷貝:
- 如果需要對文件內容做二次加工(如壓縮、加密、轉換等),就無法直接用 sendfile;
- 若數據源并非文件而是來自內存生成的動態內容,則零拷貝價值有限。
小結
重點從“計算機資源短板”角度,闡述了 CPU、內存與 I/O 三大組件易成瓶頸的典型特征,以及如何通過 Linux 工具進行初步診斷:
-
CPU
- 用
top
查看 us、sy、ni、wa、hi、si、st、id 等多維度指標; - 用
load average
結合 CPU 核心數評估任務排隊情況; - 用
vmstat
查看b
、si
/so
、cs
等指標,判斷 I/O 等待與上下文切換開銷。
- 用
-
內存
- 理解物理內存、虛擬內存(Swap)、共享內存與邏輯內存的關系;
- 關注
top
中的 VIRT/RES/SHR,重點監控進程實際占用物理內存(RES)與 Swap 使用情況; - 理解多級 CPU 緩存架構及偽共享帶來的慘重開銷,學會使用
@Contended
或手動填充(Padding)緩解問題; - 掌握 HugePage 技術以減少 TLB miss 開銷,了解 JVM
-XX:+AlwaysPreTouch
預先加載對啟動與運行性能的影響。
-
I/O
- 認識機械盤、SSD、內存之間的性能差異;
- 在
top
和vmstat
中關注wa
與b
,結合iostat
的%util
、avgqu-sz
、await
、svctm
指標判斷磁盤負載與響應狀況; - 理解零拷貝原理,掌握何時通過
sendfile
等機制減少內核與用戶空間拷貝,以提升大數據量傳輸效率。
這些方法只能幫助我們對 CPU、內存與 I/O 的基本瓶頸進行“定性判定”,但要精準到“真正的問題根源”——例如,哪個具體線程在搶占 CPU?哪個函數導致頻繁上下文切換?哪個文件或邏輯請求引起高磁盤 I/O?——還需要依賴更深入的性能分析工具(如 perf、eBPF、JVM Flight Recorder、系統追蹤工具等)收集更細粒度的數據。