一、概述
1.1 背景介紹
監(jiān)控大屏上iowait突然飆到 80%,SSH 連上去敲個(gè)ls要等 5 秒才有響應(yīng),業(yè)務(wù)日志瘋狂報(bào)超時(shí),數(shù)據(jù)庫慢查詢告警刷屏。這種場(chǎng)景在 SRE 的日常里出現(xiàn)頻率極高,尤其是跑著 MySQL、Elasticsearch、Kafka 這類重 IO 業(yè)務(wù)的機(jī)器上。CPU 看著不高,內(nèi)存也沒爆,但系統(tǒng)就是卡得像被凍住了一樣——十有八九是磁盤 IO 出了問題。
磁盤 IO 問題的棘手之處在于,它不像 CPU 打滿那樣一眼就能看出來。IO 瓶頸可能藏在文件系統(tǒng)層、Block 層、設(shè)備驅(qū)動(dòng)層,甚至是 RAID 卡的緩存策略里。一個(gè)%util100% 到底意味著什么?await高是磁盤慢還是隊(duì)列深?svctm這個(gè)指標(biāo)還能不能信?這些問題如果搞不清楚,排查就會(huì)走彎路。
這篇文章從 Linux IO 棧的全貌講起,把 iostat、iotop、blktrace、fio、bpftrace 這套工具鏈串起來,覆蓋從"發(fā)現(xiàn) IO 問題"到"定位根因"再到"調(diào)優(yōu)解決"的完整鏈路。
1.2 技術(shù)特點(diǎn)
全棧視角:從 VFS 到塊設(shè)備驅(qū)動(dòng),逐層拆解 IO 路徑,不停留在工具表面
工具鏈完整:iostat 看全局、iotop 定進(jìn)程、blktrace 追請(qǐng)求、fio 做基準(zhǔn)、bpftrace 抓延遲
面向?qū)崙?zhàn):每個(gè)工具都給出真實(shí)場(chǎng)景下的輸出解讀,不是照搬 man page
調(diào)優(yōu)閉環(huán):不只是發(fā)現(xiàn)問題,還覆蓋 readahead、dirty ratio、調(diào)度器等內(nèi)核參數(shù)調(diào)優(yōu)
1.3 適用場(chǎng)景
場(chǎng)景一:生產(chǎn)環(huán)境突發(fā) IO 飆升,系統(tǒng)響應(yīng)變慢甚至卡死,需要快速定位是哪個(gè)進(jìn)程在瘋狂讀寫
場(chǎng)景二:數(shù)據(jù)庫服務(wù)器 IO 延遲周期性升高,需要區(qū)分是磁盤性能不足還是應(yīng)用層 IO 模式有問題
場(chǎng)景三:新機(jī)器上線前需要做磁盤性能基準(zhǔn)測(cè)試,評(píng)估 SSD/HDD 是否滿足業(yè)務(wù) IOPS 和吞吐需求
場(chǎng)景四:容器環(huán)境下多個(gè) Pod 共享磁盤,需要找出 IO 資源的爭搶源頭
1.4 環(huán)境要求
| 組件 | 版本要求 | 說明 |
|---|---|---|
| 操作系統(tǒng) | Ubuntu 24.04 LTS / RHEL 9.x | 內(nèi)核 6.8+,支持 io.latency cgroup 控制器 |
| sysstat | 12.7+ | 提供 iostat、sar 等工具 |
| iotop | 0.6+ / iotop-c 1.26+ | 進(jìn)程級(jí) IO 監(jiān)控,推薦 iotop-c(C 語言重寫版) |
| blktrace | 1.3+ | 塊設(shè)備層追蹤 |
| fio | 3.37+ | 磁盤基準(zhǔn)測(cè)試 |
| bpftrace | 0.21+ | eBPF 追蹤 IO 延遲分布 |
| perf | 6.8+ | 內(nèi)核性能分析 |
二、Linux IO 棧全景與調(diào)度器
要排查 IO 問題,首先得搞清楚一個(gè) IO 請(qǐng)求從應(yīng)用程序發(fā)出到磁盤完成,中間經(jīng)過了哪些環(huán)節(jié)。不理解 IO 棧的層次結(jié)構(gòu),看 iostat 的輸出就只是在看數(shù)字。
2.1 IO 棧分層架構(gòu)
應(yīng)用程序 (read/write/pread/pwrite/io_uring) | v VFS (Virtual File System) --- Page Cache | v 文件系統(tǒng) (ext4 / xfs / btrfs) | v Block Layer (通用塊層) | - IO 合并 (merge) | - IO 調(diào)度 (mq-deadline/bfq/kyber/none) | - 請(qǐng)求隊(duì)列 (多隊(duì)列 blk-mq) v 設(shè)備驅(qū)動(dòng) (NVMe driver / SCSI / virtio-blk) | v 物理設(shè)備 (NVMe SSD / SATA SSD / HDD / 云盤)
每一層都可能成為瓶頸,排查時(shí)需要逐層排除:
VFS + Page Cache 層:大部分讀請(qǐng)求會(huì)命中 Page Cache 直接返回,根本不會(huì)到磁盤。如果free -h看到buff/cache很小,或者sar -B顯示pgpgin很高,說明緩存命中率低,大量讀請(qǐng)求穿透到了磁盤。寫請(qǐng)求默認(rèn)走 writeback 模式,先寫到 Page Cache 的臟頁里,由內(nèi)核的pdflush/flush線程異步刷盤。
文件系統(tǒng)層:ext4 的 journal 寫入、xfs 的 log 寫入都會(huì)產(chǎn)生額外的 IO。文件系統(tǒng)碎片化嚴(yán)重時(shí),順序讀會(huì)退化成隨機(jī)讀。filefrag命令可以查看文件碎片程度。
Block Layer:這是 iostat 能觀測(cè)到的層。IO 請(qǐng)求在這里被合并(相鄰的小 IO 合并成大 IO)、排序、調(diào)度。blk-mq(多隊(duì)列塊層)是 6.x 內(nèi)核的默認(rèn)架構(gòu),每個(gè) CPU 核心有獨(dú)立的軟件隊(duì)列,減少了鎖競(jìng)爭。
設(shè)備驅(qū)動(dòng)和物理設(shè)備:NVMe 設(shè)備有自己的硬件多隊(duì)列(通常 64 個(gè)隊(duì)列,每隊(duì)列 64K 深度),SATA SSD 只有單隊(duì)列(NCQ 深度 32)。這個(gè)差異直接影響并發(fā) IO 性能。
2.2 IO 調(diào)度器詳解
內(nèi)核 6.x 提供了四種 IO 調(diào)度器,針對(duì)不同設(shè)備類型選擇合適的調(diào)度器對(duì)性能影響很大。
# 查看當(dāng)前磁盤使用的調(diào)度器(方括號(hào)標(biāo)記的是當(dāng)前生效的) cat /sys/block/sda/queue/scheduler # 輸出示例:[mq-deadline] kyber bfq none # 運(yùn)行時(shí)切換調(diào)度器(立即生效,不需要重啟) echo"bfq"> /sys/block/sda/queue/scheduler
四種調(diào)度器的選型策略:
| 調(diào)度器 | 適用場(chǎng)景 | 核心機(jī)制 | 推薦設(shè)備 |
|---|---|---|---|
| none | NVMe SSD | 不做任何調(diào)度,直接下發(fā)到硬件隊(duì)列 | NVMe SSD(硬件自帶調(diào)度) |
| mq-deadline | 通用場(chǎng)景 | 按截止時(shí)間排序,防止請(qǐng)求餓死,讀優(yōu)先于寫 | SATA SSD、HDD、虛擬機(jī)云盤 |
| bfq | 桌面/混合負(fù)載 | 按進(jìn)程公平分配 IO 帶寬,類似 CPU 的 CFS | HDD、需要 IO 公平性的多租戶場(chǎng)景 |
| kyber | 低延遲 SSD | 基于目標(biāo)延遲的輕量級(jí)調(diào)度,自動(dòng)調(diào)節(jié)隊(duì)列深度 | 高性能 SATA SSD |
選型建議:NVMe SSD 直接用none,不要畫蛇添足加調(diào)度器。HDD 用mq-deadline保證讀延遲可控。多用戶共享 HDD 的場(chǎng)景(比如編譯服務(wù)器)用bfq防止某個(gè)進(jìn)程獨(dú)占 IO 帶寬。kyber在實(shí)際生產(chǎn)中用得不多,它的自適應(yīng)機(jī)制在負(fù)載波動(dòng)大的場(chǎng)景下表現(xiàn)不夠穩(wěn)定。
# 持久化調(diào)度器配置(udev 規(guī)則)
cat > /etc/udev/rules.d/60-io-scheduler.rules <'EOF'
# NVMe SSD 使用 none
ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/scheduler}="none"
# SATA SSD 使用 mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="mq-deadline"
# HDD 使用 mq-deadline
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="mq-deadline"
EOF
# 重新加載 udev 規(guī)則
udevadm control --reload-rules && udevadm trigger
2.3 SSD vs HDD 的 IO 特性差異
理解 SSD 和 HDD 的物理特性差異,是正確解讀 IO 指標(biāo)的前提。
| 特性 | HDD | SATA SSD | NVMe SSD |
|---|---|---|---|
| 隨機(jī)讀 IOPS | 100-200 | 30K-90K | 200K-1M+ |
| 隨機(jī)寫 IOPS | 100-200 | 20K-70K | 100K-500K+ |
| 順序讀吞吐 | 150-250 MB/s | 500-560 MB/s | 3-7 GB/s |
| 順序?qū)懲掏?/td> | 150-250 MB/s | 400-530 MB/s | 2-5 GB/s |
| 平均延遲 | 5-15ms | 0.05-0.1ms | 0.01-0.03ms |
| 隊(duì)列深度 | 1(NCQ=32) | 32(NCQ) | 64K x 多隊(duì)列 |
| %util 參考意義 | 高(機(jī)械臂同一時(shí)刻只能服務(wù)一個(gè)位置) | 低(內(nèi)部并行度高) | 極低(不要看這個(gè)指標(biāo)) |
這里有一個(gè)非常關(guān)鍵的認(rèn)知:**%util對(duì) SSD 幾乎沒有參考價(jià)值**。HDD 是單通道設(shè)備,%util100% 確實(shí)意味著磁盤忙不過來。但 SSD 內(nèi)部有大量并行通道,%util100% 可能只用了實(shí)際能力的 10%。判斷 SSD 是否到達(dá)瓶頸,應(yīng)該看await(IO 延遲)和實(shí)際 IOPS 是否接近設(shè)備標(biāo)稱值。
三、iostat 輸出深度解讀
iostat 是 IO 排查的第一站,但它的輸出字段很多,不少人只會(huì)看%util,這遠(yuǎn)遠(yuǎn)不夠。
3.1 iostat 基礎(chǔ)用法
# 最常用的命令:每秒刷新一次,顯示擴(kuò)展信息,單位用 MB # -x 擴(kuò)展模式 -m 單位MB -t 顯示時(shí)間戳 1 間隔1秒 iostat -xmt 1 # 只看特定磁盤 iostat -xmt -d nvme0n1 sda 1 # 看第一次輸出要注意:第一行是系統(tǒng)啟動(dòng)以來的累計(jì)平均值,不是實(shí)時(shí)數(shù)據(jù) # 從第二行開始才是每秒的實(shí)時(shí)數(shù)據(jù),排查問題時(shí)忽略第一行
3.2 輸出字段逐個(gè)拆解
一條典型的 iostat -x 輸出:
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz d/s dkB/s drqm/s %drqm d_await dareq-sz f/s f_await aqu-sz %util sda 850.00 13600.0 45.00 5.03 0.85 16.00 320.00 25600.0 120.00 27.27 2.30 80.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 1.25 78.40
核心指標(biāo)解讀:
| 指標(biāo) | 含義 | 怎么看 |
|---|---|---|
| r/s /w/s | 每秒完成的讀/寫請(qǐng)求數(shù)(IOPS) | HDD 超過 150 就要警惕,NVMe 到 10 萬都正常 |
| rkB/s /wkB/s | 每秒讀/寫的數(shù)據(jù)量(吞吐) | 對(duì)比磁盤標(biāo)稱帶寬,接近上限說明帶寬打滿 |
| rrqm/s /wrqm/s | 每秒合并的讀/寫請(qǐng)求數(shù) | 合并率高說明應(yīng)用的 IO 模式比較友好(順序或相鄰) |
| r_await /w_await | 讀/寫請(qǐng)求的平均耗時(shí)(ms),包含隊(duì)列等待+設(shè)備服務(wù)時(shí)間 | 最重要的延遲指標(biāo) 。HDD 正常 5-15ms,SSD 正常 0.05-0.5ms |
| aqu-sz | 平均請(qǐng)求隊(duì)列長度 | 隊(duì)列長說明設(shè)備處理不過來,請(qǐng)求在排隊(duì) |
| %util | 設(shè)備繁忙時(shí)間百分比 | HDD 有參考意義,SSD 參考價(jià)值低(前面解釋過) |
| rareq-sz /wareq-sz | 平均請(qǐng)求大小(KB) | 判斷 IO 模式:4-8KB 是典型隨機(jī) IO,128KB+ 是順序 IO |
已廢棄的 svctm:老版本 iostat 有svctm(設(shè)備服務(wù)時(shí)間)字段,sysstat 12.x 已經(jīng)標(biāo)記為不可靠并計(jì)劃移除。這個(gè)值是用%util / (r/s + w/s)反算出來的,在多隊(duì)列設(shè)備上完全失真。不要再用svctm做任何判斷。
3.3 iostat 實(shí)戰(zhàn)判斷模板
# 場(chǎng)景判斷速查: # 1. await 高 + aqu-sz 高 + %util 高 → 磁盤確實(shí)忙不過來(HDD 常見) # 2. await 高 + aqu-sz 低 + %util 低 → 單個(gè) IO 慢,可能是磁盤硬件問題或 RAID 降級(jí) # 3. await 正常 + w/s 極高 + wkB/s 低 → 大量小寫入,考慮合并 IO 或調(diào)大 dirty ratio # 4. rrqm/s 接近 0 + rareq-sz 很小 → 純隨機(jī)讀,Page Cache 沒起作用 # 5. w_await 遠(yuǎn)高于 r_await → 寫入瓶頸,檢查 fsync 頻率和 journal 模式
3.4 用 sar 看 IO 歷史趨勢(shì)
iostat 只能看實(shí)時(shí)數(shù)據(jù),要回溯歷史得靠 sar。sysstat 默認(rèn)每 10 分鐘采集一次數(shù)據(jù),保存在/var/log/sysstat/或/var/log/sa/下。
# 查看今天的磁盤 IO 歷史(-d 磁盤統(tǒng)計(jì),-p 顯示設(shè)備名) sar -dp 0 # 查看昨天的數(shù)據(jù) sar -dp -f /var/log/sysstat/sa$(date -d yesterday +%d) # 查看指定時(shí)間段 sar -dp -s 0200 -e 0400 # 輸出示例: # 0201 DEV tps rkB/s wkB/s areq-sz aqu-sz await %util # 0201 sda 1250.00 10000.00 40000.00 40.00 3.50 2.80 92.00 # 0201 sda 120.00 960.00 2400.00 28.00 0.15 1.25 12.00
凌晨 2:10 到 2:20 之間 IO 明顯飆升,tps 從 120 跳到 1250,%util從 12% 到 92%。結(jié)合業(yè)務(wù)日志看這個(gè)時(shí)間段在做什么——大概率是定時(shí)任務(wù)(備份、日志輪轉(zhuǎn)、ETL 作業(yè))。
四、iotop 定位 IO 密集進(jìn)程
iostat 告訴你磁盤整體很忙,但不告訴你是誰在讀寫。這時(shí)候需要 iotop 來定位到具體進(jìn)程。
4.1 iotop 基礎(chǔ)用法
# 需要 root 權(quán)限(依賴內(nèi)核的 taskstats 接口) # -o 只顯示有 IO 活動(dòng)的進(jìn)程(過濾掉空閑的) # -P 顯示進(jìn)程而不是線程 # -a 累積模式(顯示自啟動(dòng)以來的累計(jì) IO 量) sudo iotop -oP # 推薦使用 iotop-c(C 語言重寫版,性能更好) # Ubuntu: sudo apt install iotop-c sudo iotop-c -oP
4.2 iotop 輸出解讀
Total DISK READ: 125.50 M/s | Total DISK WRITE: 42.30 M/s Actual DISK READ: 125.50 M/s | Actual DISK WRITE: 8.75 M/s TID PRIO USER DISK READ DISK WRITE SWAPIN IO> COMMAND 12847 be/4 mysql 98.20 M/s 5.60 M/s 0.00 % 82.35 % mysqld --defaults-file=/etc/mysql/my.cnf 3021 be/4 root 22.10 M/s 0.00 B/s 0.00 % 15.20 % tar czf /backup/db-20260206.tar.gz /var/lib/mysql 891 be/4 elastic 5.20 M/s 36.70 M/s 0.00 % 8.50 % java -Xms16g ... elasticsearch
幾個(gè)關(guān)鍵信息:
Total DISK READ/WRITE:所有進(jìn)程請(qǐng)求的 IO 總量(經(jīng)過 Page Cache 之前)
Actual DISK READ/WRITE:實(shí)際落到磁盤的 IO 量。Actual WRITE 遠(yuǎn)小于 Total WRITE 是正常的,因?yàn)閷懭胂鹊?Page Cache,異步刷盤
**IO>**:該進(jìn)程等待 IO 的時(shí)間占比,這個(gè)值高說明進(jìn)程被 IO 阻塞了
PRIO:IO 優(yōu)先級(jí),be/4表示 best-effort 類第 4 級(jí)(默認(rèn)值)
上面的輸出一眼就能看出:mysql 進(jìn)程在瘋狂讀數(shù)據(jù)(98 MB/s),同時(shí)有個(gè) tar 備份任務(wù)也在讀(22 MB/s)。兩個(gè)大讀取任務(wù)疊加,磁盤帶寬被打滿了。
4.3 用 ionice 調(diào)整 IO 優(yōu)先級(jí)
找到了搗亂的進(jìn)程,如果不能直接 kill,可以用 ionice 降低它的 IO 優(yōu)先級(jí):
# IO 調(diào)度類: # 1 = Realtime(實(shí)時(shí),慎用) # 2 = Best-effort(默認(rèn),0-7 級(jí),數(shù)字越小優(yōu)先級(jí)越高) # 3 = Idle(空閑時(shí)才執(zhí)行,適合備份任務(wù)) # 把備份進(jìn)程降到 Idle 級(jí)別(只在磁盤空閑時(shí)才給它 IO) sudo ionice -c 3 -p 3021 # 啟動(dòng)備份任務(wù)時(shí)直接指定低優(yōu)先級(jí) sudo ionice -c 3 nice -n 19 tar czf /backup/db-20260206.tar.gz /var/lib/mysql # 注意:ionice 只在 bfq 和 mq-deadline 調(diào)度器下生效 # none 調(diào)度器(NVMe 默認(rèn))不支持 IO 優(yōu)先級(jí)
4.4 pidstat 補(bǔ)充進(jìn)程級(jí) IO 統(tǒng)計(jì)
iotop 是交互式的,不方便腳本化采集。pidstat 可以按固定間隔輸出進(jìn)程 IO 數(shù)據(jù):
# -d 顯示 IO 統(tǒng)計(jì) 1 每秒采集 10 采集10次 pidstat -d 1 10 # 只看特定進(jìn)程 pidstat -d -p 12847 1 # 輸出示例: # Time UID PID kB_rd/s kB_wr/s kB_ccwr/s iodelay Command # 1501 999 12847 98200.00 5600.00 800.00 45 mysqld
kB_ccwr/s是被取消的寫入量(寫入 Page Cache 后又被覆蓋,沒有實(shí)際落盤),iodelay是進(jìn)程因?yàn)?IO 等待而被延遲的 tick 數(shù)。
五、blktrace + btt 深度分析
iostat 和 iotop 能解決 80% 的 IO 問題,但遇到復(fù)雜場(chǎng)景——比如需要知道 IO 請(qǐng)求在塊層各個(gè)階段分別花了多少時(shí)間——就需要 blktrace 出場(chǎng)了。
5.1 blktrace 工作原理
blktrace 在內(nèi)核的塊層埋了追蹤點(diǎn),記錄每個(gè) IO 請(qǐng)求的完整生命周期:
Q (Queued) → 請(qǐng)求進(jìn)入塊層隊(duì)列 G (Get request) → 分配 request 結(jié)構(gòu)體 M (Merged) → 與已有請(qǐng)求合并 I (Inserted) → 插入調(diào)度器隊(duì)列 D (Dispatched) → 下發(fā)到設(shè)備驅(qū)動(dòng) C (Completed) → 設(shè)備完成 IO
每個(gè)階段之間的時(shí)間差就是該階段的耗時(shí)。Q→C是總耗時(shí),D→C是設(shè)備實(shí)際服務(wù)時(shí)間,Q→D是在軟件層排隊(duì)和調(diào)度的時(shí)間。
5.2 blktrace 實(shí)戰(zhàn)
# 采集 sda 的塊層追蹤數(shù)據(jù),持續(xù) 10 秒 sudo blktrace -d /dev/sda -w 10 -o trace # 會(huì)生成 trace.blktrace.0, trace.blktrace.1 ... 每個(gè) CPU 一個(gè)文件 # 用 blkparse 解析成可讀格式 blkparse -i trace -o trace.txt # 輸出示例(每行一個(gè)事件): # 8,0 1 1 0.000000000 12847 Q R 123456 + 8 [mysqld] # 8,0 1 2 0.000001200 12847 G R 123456 + 8 [mysqld] # 8,0 1 3 0.000002500 12847 I R 123456 + 8 [mysqld] # 8,0 1 4 0.000015000 12847 D R 123456 + 8 [mysqld] # 8,0 1 5 0.000850000 0 C R 123456 + 8 [0] # 解讀:mysqld 發(fā)起了一個(gè)讀請(qǐng)求,扇區(qū) 123456 開始讀 8 個(gè)扇區(qū)(4KB) # Q→D 排隊(duì) 15us,D→C 設(shè)備服務(wù) 835us,總耗時(shí) 850us
5.3 btt 統(tǒng)計(jì)分析
逐行看 blkparse 輸出不現(xiàn)實(shí),btt 工具可以自動(dòng)統(tǒng)計(jì)各階段的延遲分布:
# 用 btt 分析(需要先用 blkparse 生成二進(jìn)制格式) blkparse -i trace -d trace.bin btt -i trace.bin # btt 輸出的關(guān)鍵段落: # ==================== All Devices ==================== # ALL MIN AVG MAX N # --------------- ------------- ------------- ------------- ----------- # Q2C 0.000085000 0.001250000 0.025000000 12847 # Q2D 0.000005000 0.000018000 0.000350000 12847 # D2C 0.000080000 0.001232000 0.024800000 12847 # Q2C = 總延遲(隊(duì)列到完成) # Q2D = 軟件層延遲(隊(duì)列到下發(fā)) # D2C = 硬件層延遲(下發(fā)到完成)
上面的數(shù)據(jù)說明:平均總延遲 1.25ms,其中軟件層只占 0.018ms,硬件層占 1.232ms。瓶頸在設(shè)備本身,不在內(nèi)核調(diào)度。如果反過來 Q2D 很大而 D2C 很小,說明是調(diào)度器或隊(duì)列配置有問題。
5.4 iowatcher 可視化
blktrace 的數(shù)據(jù)還可以用 iowatcher 生成可視化圖表,直觀展示 IO 模式:
# 安裝 iowatcher sudo apt install iowatcher # 生成 SVG 圖表 iowatcher -t trace -o io-pattern.svg # 圖表包含:IOPS 時(shí)間線、吞吐時(shí)間線、IO 延遲分布、IO 偏移量分布(可以看出是順序還是隨機(jī))
六、fio 磁盤性能基準(zhǔn)測(cè)試
排查 IO 問題時(shí)經(jīng)常需要回答一個(gè)基本問題:這塊磁盤到底能跑多快?是磁盤本身性能不行,還是應(yīng)用的 IO 模式有問題?fio 是回答這個(gè)問題的標(biāo)準(zhǔn)工具。
6.1 fio 核心參數(shù)
# 安裝 fio sudo apt install fio # Debian/Ubuntu sudo dnf install fio # RHEL/Fedora
fio 的參數(shù)很多,但核心就這幾個(gè):
| 參數(shù) | 含義 | 常用值 |
|---|---|---|
| --rw | IO 模式 | read /write/randread/randwrite/randrw |
| --bs | 塊大小 | 4k (數(shù)據(jù)庫隨機(jī)IO)、128k/1m(順序IO) |
| --iodepth | 隊(duì)列深度 | HDD 用 1-4,SATA SSD 用 32,NVMe 用 64-128 |
| --ioengine | IO 引擎 | libaio (Linux AIO)、io_uring(推薦,內(nèi)核 6.x) |
| --numjobs | 并發(fā)任務(wù)數(shù) | 通常 1-4,測(cè)多隊(duì)列性能時(shí)增大 |
| --size | 測(cè)試文件大小 | 至少是內(nèi)存的 2 倍,避免 Page Cache 干擾 |
| --direct | 繞過 Page Cache | 1 (基準(zhǔn)測(cè)試必須開啟) |
| --runtime | 運(yùn)行時(shí)長 | 60-120 秒,太短數(shù)據(jù)不穩(wěn)定 |
6.2 標(biāo)準(zhǔn)測(cè)試場(chǎng)景
# 場(chǎng)景1:隨機(jī)讀 IOPS(模擬數(shù)據(jù)庫查詢) fio --name=rand-read --ioengine=io_uring --rw=randread --bs=4k --iodepth=64 --numjobs=4 --size=4G --direct=1 --runtime=60 --group_reporting --filename=/dev/nvme0n1 # 注意:直接測(cè)裸設(shè)備會(huì)銷毀數(shù)據(jù)!測(cè)試用文件更安全 # 更安全的方式:在文件系統(tǒng)上測(cè)試 fio --name=rand-read --ioengine=io_uring --rw=randread --bs=4k --iodepth=64 --numjobs=4 --size=4G --direct=1 --runtime=60 --group_reporting --directory=/mnt/test # 場(chǎng)景2:隨機(jī)寫 IOPS(模擬數(shù)據(jù)庫寫入) fio --name=rand-write --ioengine=io_uring --rw=randwrite --bs=4k --iodepth=64 --numjobs=4 --size=4G --direct=1 --runtime=60 --group_reporting --directory=/mnt/test # 場(chǎng)景3:順序讀吞吐(模擬大文件掃描、備份) fio --name=seq-read --ioengine=io_uring --rw=read --bs=1m --iodepth=16 --numjobs=1 --size=8G --direct=1 --runtime=60 --group_reporting --directory=/mnt/test # 場(chǎng)景4:混合隨機(jī)讀寫 7:3(模擬 OLTP 數(shù)據(jù)庫) fio --name=mixed-rw --ioengine=io_uring --rw=randrw --rwmixread=70 --bs=4k --iodepth=32 --numjobs=4 --size=4G --direct=1 --runtime=60 --group_reporting --directory=/mnt/test
6.3 fio 輸出解讀
rand-read: (groupid=0,jobs=4): err= 0: pid=5678 read: IOPS=185.2k, BW=723MiB/s (758MB/s) slat (nsec): min=1200, max=85000, avg=2850.00, stdev=1200.00 clat (usec): min=45, max=12500, avg=1350.00, stdev=680.00 lat (usec): min=48, max=12520, avg=1353.00, stdev=681.00 clat percentiles (usec): | 1.00th=[ 120], 5.00th=[ 245], 10.00th=[ 400], | 50.00th=[ 1150], 90.00th=[ 2350], 95.00th=[ 2900], | 99.00th=[ 4500], 99.50th=[ 5800], 99.99th=[10800] bw ( KiB/s): min=680000, max=760000, per=100.00%, avg=740800.00 iops : min=170000, max=190000, avg=185200.00
關(guān)鍵指標(biāo):
IOPS=185.2k:每秒 18.5 萬次隨機(jī)讀,這是一塊性能不錯(cuò)的 NVMe SSD
slat(submission latency):提交延遲,從應(yīng)用發(fā)起到進(jìn)入內(nèi)核,通常在微秒級(jí)
clat(completion latency):完成延遲,從進(jìn)入內(nèi)核到 IO 完成,這是最關(guān)注的指標(biāo)
lat:總延遲 = slat + clat
clat percentiles:延遲分位數(shù),P99=4.5ms 說明 99% 的請(qǐng)求在 4.5ms 內(nèi)完成。關(guān)注 P99 和 P99.9,平均值會(huì)掩蓋長尾延遲
6.4 io_uring vs libaio
內(nèi)核 6.x 環(huán)境下強(qiáng)烈推薦使用io_uring引擎替代傳統(tǒng)的libaio:
# 對(duì)比測(cè)試:同樣參數(shù),只換引擎 # libaio fio --name=aio-test --ioengine=libaio --rw=randread --bs=4k --iodepth=128 --numjobs=1 --size=4G --direct=1 --runtime=30 --directory=/mnt/test # io_uring fio --name=uring-test --ioengine=io_uring --rw=randread --bs=4k --iodepth=128 --numjobs=1 --size=4G --direct=1 --runtime=30 --directory=/mnt/test
io_uring 的優(yōu)勢(shì)在于減少了系統(tǒng)調(diào)用次數(shù)(通過共享內(nèi)存的提交/完成隊(duì)列),在高 IOPS 場(chǎng)景下能比 libaio 高出 10%-30% 的性能。現(xiàn)代數(shù)據(jù)庫(如 PostgreSQL 16+、RocksDB)已經(jīng)原生支持 io_uring。
七、文件系統(tǒng)選擇
文件系統(tǒng)是 IO 棧中直接影響性能的一層,選錯(cuò)文件系統(tǒng)可能導(dǎo)致性能差距達(dá)到 2-3 倍。
7.1 ext4 / xfs / btrfs 對(duì)比
| 特性 | ext4 | xfs | btrfs |
|---|---|---|---|
| 最大文件系統(tǒng) | 1 EB | 8 EB | 16 EB |
| 最大單文件 | 16 TB | 8 EB | 16 EB |
| 元數(shù)據(jù)日志 | 有序/回寫 | 有序 | CoW(無傳統(tǒng)日志) |
| 在線擴(kuò)容 | 支持 | 支持 | 支持 |
| 在線縮容 | 支持 | 不支持 | 支持 |
| 快照 | 不支持 | 不支持 | 原生支持 |
| 透明壓縮 | 不支持 | 不支持 | 支持(zstd/lzo) |
| 小文件性能 | 優(yōu)秀 | 良好 | 一般 |
| 大文件順序?qū)?/td> | 良好 | 優(yōu)秀 | 良好 |
| 并發(fā)寫入 | 一般(單 journal) | 優(yōu)秀(延遲分配) | 良好 |
| 生產(chǎn)穩(wěn)定性 | 極高 | 極高 | 高(6.x 內(nèi)核已成熟) |
選型建議:
數(shù)據(jù)庫服務(wù)器(MySQL/PostgreSQL):xfs。延遲分配和優(yōu)秀的并發(fā)寫入性能對(duì)數(shù)據(jù)庫友好,RHEL 默認(rèn)文件系統(tǒng)
通用 Linux 服務(wù)器:ext4。最穩(wěn)定、最成熟、調(diào)優(yōu)資料最多,Ubuntu 默認(rèn)文件系統(tǒng)
需要快照/壓縮的場(chǎng)景(日志存儲(chǔ)、容器存儲(chǔ)):btrfs。透明壓縮可以節(jié)省 30%-50% 的磁盤空間,快照功能方便備份
Kubernetes 節(jié)點(diǎn):xfs。containerd/CRI-O 的 overlayfs 在 xfs 上表現(xiàn)更好
7.2 文件系統(tǒng)掛載參數(shù)調(diào)優(yōu)
# ext4 高性能掛載參數(shù) mount -o noatime,nodiratime,barrier=0,data=writeback /dev/sda1 /data # noatime: 不更新訪問時(shí)間戳,減少寫入 # nodiratime: 不更新目錄訪問時(shí)間 # barrier=0: 關(guān)閉寫屏障(僅在有 BBU 的 RAID 卡上使用,否則斷電丟數(shù)據(jù)) # data=writeback: 元數(shù)據(jù)日志模式,比默認(rèn)的 ordered 快但斷電風(fēng)險(xiǎn)略高 # xfs 高性能掛載參數(shù) mount -o noatime,logbufs=8,logbsize=256k /dev/sda1 /data # logbufs=8: 增大日志緩沖區(qū)數(shù)量 # logbsize=256k: 增大日志緩沖區(qū)大小 # /etc/fstab 持久化配置 /dev/nvme0n1p1 /data xfs defaults,noatime,logbufs=8,logbsize=256k 0 2
八、IO 性能調(diào)優(yōu)
8.1 readahead 預(yù)讀調(diào)優(yōu)
預(yù)讀是內(nèi)核在檢測(cè)到順序讀模式時(shí),提前讀取后續(xù)數(shù)據(jù)到 Page Cache 的機(jī)制。對(duì)順序讀密集的場(chǎng)景(日志分析、數(shù)據(jù)導(dǎo)出)效果顯著。
# 查看當(dāng)前預(yù)讀值(單位:512字節(jié)扇區(qū),默認(rèn) 256 = 128KB)
blockdev --getra /dev/sda
# 調(diào)大預(yù)讀值(適合順序讀場(chǎng)景,如 Kafka、HDFS)
sudo blockdev --setra 2048 /dev/sda # 1MB 預(yù)讀
# 調(diào)小預(yù)讀值(適合純隨機(jī)讀場(chǎng)景,如數(shù)據(jù)庫 OLTP)
sudo blockdev --setra 64 /dev/sda # 32KB 預(yù)讀
# 持久化配置(udev 規(guī)則)
echo'ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{bdi/read_ahead_kb}="1024"'
> /etc/udev/rules.d/61-readahead.rules
8.2 dirty ratio 臟頁參數(shù)調(diào)優(yōu)
臟頁參數(shù)控制 Page Cache 中臟數(shù)據(jù)的刷盤策略,直接影響寫入性能和數(shù)據(jù)安全性。
# 查看當(dāng)前臟頁參數(shù) sysctl vm.dirty_ratio vm.dirty_background_ratio vm.dirty_expire_centisecs vm.dirty_writeback_centisecs # 參數(shù)說明: # vm.dirty_ratio = 20 # 臟頁占內(nèi)存 20% 時(shí),寫入進(jìn)程被阻塞,同步刷盤(硬上限) # vm.dirty_background_ratio = 10 # 臟頁占內(nèi)存 10% 時(shí),后臺(tái)線程開始異步刷盤(軟上限) # vm.dirty_expire_centisecs = 3000 # 臟頁超過 30 秒必須刷盤 # vm.dirty_writeback_centisecs = 500 # 后臺(tái)刷盤線程每 5 秒喚醒一次
不同場(chǎng)景的調(diào)優(yōu)策略:
# 數(shù)據(jù)庫服務(wù)器(低延遲優(yōu)先,減少突發(fā)刷盤導(dǎo)致的延遲毛刺) sysctl -w vm.dirty_ratio=5 sysctl -w vm.dirty_background_ratio=2 sysctl -w vm.dirty_expire_centisecs=1000 sysctl -w vm.dirty_writeback_centisecs=100 # 日志/流式寫入服務(wù)器(吞吐優(yōu)先,允許更多臟頁緩沖) sysctl -w vm.dirty_ratio=40 sysctl -w vm.dirty_background_ratio=20 sysctl -w vm.dirty_expire_centisecs=6000 sysctl -w vm.dirty_writeback_centisecs=500 # 持久化到 /etc/sysctl.d/60-io-tuning.conf cat > /etc/sysctl.d/60-io-tuning.conf <'EOF' vm.dirty_ratio = 5 vm.dirty_background_ratio = 2 vm.dirty_expire_centisecs = 1000 vm.dirty_writeback_centisecs = 100 EOF sysctl --system
8.3 隊(duì)列深度與 nr_requests 調(diào)優(yōu)
# 查看塊設(shè)備隊(duì)列深度 cat /sys/block/sda/queue/nr_requests # 默認(rèn)值通常是 256 # NVMe 設(shè)備的硬件隊(duì)列深度 cat /sys/block/nvme0n1/queue/nr_requests # 對(duì)于高并發(fā) IO 場(chǎng)景,可以適當(dāng)增大隊(duì)列深度 echo1024 > /sys/block/sda/queue/nr_requests # 查看當(dāng)前 IO 合并策略 cat /sys/block/sda/queue/nomerges # 0 = 允許合并(默認(rèn)) 1 = 禁止前向合并 2 = 完全禁止合并 # 純隨機(jī) IO 場(chǎng)景可以設(shè)為 2,省去合并檢查的開銷
8.4 cgroup v2 IO 限制
在多租戶或容器環(huán)境下,用 cgroup v2 的 io 控制器限制進(jìn)程的 IO 帶寬和 IOPS:
# 查看設(shè)備號(hào)(major:minor) ls -l /dev/sda # brw-rw---- 1 root disk 8, 0 ... → 設(shè)備號(hào) 8:0 # 創(chuàng)建 cgroup 并設(shè)置 IO 限制 mkdir -p /sys/fs/cgroup/backup-jobs echo"+io"> /sys/fs/cgroup/backup-jobs/cgroup.subtree_control # 限制 sda 上的讀寫帶寬為 50MB/s,IOPS 為 1000 echo"8:0 rbps=52428800 wbps=52428800 riops=1000 wiops=1000" > /sys/fs/cgroup/backup-jobs/io.max # 把備份進(jìn)程加入這個(gè) cgroup echo$BACKUP_PID> /sys/fs/cgroup/backup-jobs/cgroup.procs # Kubernetes 中通過 Pod 的 resources 或 annotation 配置 # 也可以用 io.latency 控制器設(shè)置延遲目標(biāo) echo"8:0 target=5000"> /sys/fs/cgroup/db-workload/io.latency # 保證 db-workload 組的 IO 延遲不超過 5ms,其他組讓路
九、常見 IO 問題排查案例
9.1 案例一:凌晨定時(shí)任務(wù)導(dǎo)致數(shù)據(jù)庫延遲飆升
現(xiàn)象:每天凌晨 230,MySQL 慢查詢數(shù)量暴增 10 倍,await從 0.5ms 飆到 15ms。
排查過程:
# 1. 先看 iostat 確認(rèn) IO 確實(shí)有問題 iostat -xmt 1 # 發(fā)現(xiàn) w_await 從 0.5ms 漲到 15ms,wkB/s 從 20MB/s 漲到 180MB/s # 2. iotop 找出誰在寫 sudo iotop -oP # 發(fā)現(xiàn)兩個(gè)大戶: # mysqld → 20MB/s 寫入(正常業(yè)務(wù)) # rsync → 160MB/s 讀?。▊浞萑蝿?wù)在全量拷貝數(shù)據(jù)目錄) # 3. 確認(rèn) rsync 是定時(shí)任務(wù)觸發(fā)的 ps aux | grep rsync # root 5432 rsync -avz /var/lib/mysql/ backup-server:/backup/mysql/ # 4. rsync 的大量順序讀把磁盤帶寬吃滿,MySQL 的隨機(jī) IO 被擠壓
解決方案:
# 方案1:用 ionice 降低備份任務(wù)優(yōu)先級(jí) ionice -c 3 nice -n 19 rsync -avz /var/lib/mysql/ backup-server:/backup/mysql/ # 方案2:用 cgroup v2 限制備份任務(wù)的 IO 帶寬 echo"8:0 rbps=52428800 wbps=52428800"> /sys/fs/cgroup/backup/io.max # 方案3:用 rsync 的 --bwlimit 限制傳輸速率 rsync -avz --bwlimit=50000 /var/lib/mysql/ backup-server:/backup/mysql/ # --bwlimit 單位是 KB/s,50000 = 50MB/s
9.2 案例二:ext4 journal 寫放大導(dǎo)致寫入性能驟降
現(xiàn)象:一臺(tái)跑 Elasticsearch 的機(jī)器,寫入吞吐突然從 200MB/s 降到 40MB/s,iostat 顯示w/s很高但wareq-sz只有 4KB。
排查過程:
# 1. iostat 看寫入模式
iostat -xmt -d sda 1
# w/s=8500 wkB/s=34000 wareq-sz=4.0 w_await=3.5 %util=98
# 大量 4KB 小寫入,這不像 ES 的正常行為(ES 的 segment merge 是大塊順序?qū)懀?
# 2. 用 blktrace 看寫入來源
sudo blktrace -d /dev/sda -w 5 -o journal-trace
blkparse -i journal-trace | grep"W"| awk'{print $NF}'| sort | uniq -c | sort -rn | head
# 發(fā)現(xiàn)大量寫入來自 [jbd2/sda1-8],這是 ext4 的 journal 線程
# 3. 檢查文件系統(tǒng) journal 模式
tune2fs -l /dev/sda1 | grep"Journal"
# Default mount options: journal_data
# journal_data 模式會(huì)把所有數(shù)據(jù)都寫一遍 journal,寫放大 2 倍!
解決方案:
# 切換到 ordered 模式(只對(duì)元數(shù)據(jù)做 journal,數(shù)據(jù)直接寫) sudo mount -o remount,data=ordered /data # 或者在 /etc/fstab 中修改 /dev/sda1 /data ext4 defaults,noatime,data=ordered 0 2 # 如果是 ES 這種自己管理數(shù)據(jù)一致性的應(yīng)用,甚至可以用 writeback 模式 # 但要確保有 UPS 或 BBU,否則斷電可能丟數(shù)據(jù)
9.3 案例三:NVMe SSD %util 100% 但實(shí)際遠(yuǎn)未飽和
現(xiàn)象:監(jiān)控告警 NVMe 磁盤%util持續(xù) 100%,但業(yè)務(wù)沒有感知到任何延遲。
排查過程:
# 1. iostat 看詳細(xì)指標(biāo) iostat -xmt -d nvme0n1 1 # r/s=45000 rkB/s=180000 r_await=0.08 aqu-sz=3.6 %util=100 # r_await 只有 0.08ms(80 微秒),完全正常 # IOPS 45K,這塊 NVMe 標(biāo)稱能跑 500K IOPS,遠(yuǎn)沒到極限 # 2. 這是 %util 計(jì)算方式的問題 # %util = (IO 請(qǐng)求數(shù) * 每次 IO 耗時(shí)) / 采樣間隔 # 當(dāng)并發(fā) IO 足夠多時(shí),即使每個(gè) IO 很快,%util 也會(huì)算到 100% # 對(duì)于多隊(duì)列設(shè)備,%util 100% 不代表設(shè)備飽和
結(jié)論:這是一個(gè)誤告警。對(duì) NVMe SSD 應(yīng)該基于await和 IOPS 來判斷是否飽和,而不是%util。監(jiān)控告警規(guī)則需要調(diào)整:
# Prometheus 告警規(guī)則(修正版) # 不再用 %util 告警 NVMe 設(shè)備,改用 await -alert:DiskIOHighLatency expr:| rate(node_disk_io_time_weighted_seconds_total[5m]) / rate(node_disk_io_time_seconds_total[5m]) > 0.005 for:5m labels: severity:warning annotations: summary:"磁盤 IO 平均延遲超過 5ms"
十、bpftrace 追蹤 IO 延遲
iostat 給的是平均值,blktrace 數(shù)據(jù)量太大不適合長期運(yùn)行。bpftrace 可以用極低的開銷實(shí)時(shí)追蹤 IO 延遲分布,是定位長尾延遲問題的利器。
10.1 biolatency:IO 延遲直方圖
# bcc-tools 自帶的 biolatency 腳本(最簡單的用法) sudo biolatency-bpfcc -D 1 # -D 按磁盤分別統(tǒng)計(jì) 1 每秒輸出一次 # 輸出示例: # disk = nvme0n1 # usecs : count distribution # 0 -> 1 : 0 | | # 2 -> 3 : 0 | | # 4 -> 7 : 125 |** | # 8 -> 15 : 2840 |****************************************| # 16 -> 31 : 1950 |*************************** | # 32 -> 63 : 680 |********* | # 64 -> 127 : 245 |*** | # 128 -> 255 : 42 | | # 256 -> 511 : 8 | | # 512 -> 1023 : 3 | | # 1024 -> 2047 : 1 | | # 大部分 IO 在 8-31 微秒完成,但有少量請(qǐng)求到了毫秒級(jí)——這就是長尾延遲
10.2 自定義 bpftrace 腳本:按進(jìn)程追蹤 IO 延遲
biolatency 只能看全局分布,如果要按進(jìn)程區(qū)分,需要自己寫 bpftrace 腳本:
# 保存為 io-latency-by-process.bt
sudo bpftrace -e'
tracepointblock_rq_issue
{
@start[args->dev, args->sector] = nsecs;
@comm[args->dev, args->sector] = comm;
}
tracepointblock_rq_complete
/@start[args->dev, args->sector]/
{
$lat = (nsecs - @start[args->dev, args->sector]) / 1000; // 轉(zhuǎn)換為微秒
@latency[@comm[args->dev, args->sector]] = hist($lat);
delete(@start[args->dev, args->sector]);
delete(@comm[args->dev, args->sector]);
}
END
{
clear(@start);
clear(@comm);
}
'
# 輸出會(huì)按進(jìn)程名分別顯示延遲直方圖:
# @latency[mysqld]:
# [8, 16) 1250 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
# [16, 32) 680 |@@@@@@@@@@@@@@@@@@@@@ |
# ...
# @latency[rsync]:
# [128, 256) 420 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
# [256, 512) 380 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
# ...
10.3 biosnoop:逐條追蹤 IO 請(qǐng)求
當(dāng)需要看每一條 IO 請(qǐng)求的詳細(xì)信息時(shí),用 biosnoop:
# 追蹤所有 IO 請(qǐng)求,顯示進(jìn)程名、延遲、扇區(qū)等 sudo biosnoop-bpfcc -d nvme0n1 # 輸出示例: # TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms) # 0.000 mysqld 12847 nvme0n1 R 12345678 4096 0.08 # 0.001 mysqld 12847 nvme0n1 R 12345686 4096 0.09 # 0.003 jbd2/sda1-8 891 nvme0n1 W 98765432 16384 0.15 # 0.850 rsync 5432 nvme0n1 R 55555555 131072 0.12 # 只看延遲超過 1ms 的慢 IO sudo biosnoop-bpfcc -d nvme0n1 -Q | awk'$NF > 1.0'
10.4 ext4slower / xfs_slower:文件系統(tǒng)級(jí)慢 IO 追蹤
有時(shí)候塊設(shè)備層延遲正常,但文件系統(tǒng)層有額外開銷(鎖競(jìng)爭、journal 等待)。bcc-tools 提供了文件系統(tǒng)級(jí)別的慢操作追蹤:
# 追蹤 ext4 上超過 1ms 的操作 sudo ext4slower-bpfcc 1 # 追蹤 xfs 上超過 1ms 的操作 sudo xfsslower-bpfcc 1 # 輸出示例: # TIME COMM PID T BYTES OFF_KB LAT(ms) FILENAME # 1501 mysqld 12847 R 16384 1024 2.35 ibdata1 # 1501 mysqld 12847 S 0 0 5.80 ib_logfile0 # T 列:R=read W=write O=open S=fsync # fsync 延遲 5.8ms,這可能是 journal 寫入導(dǎo)致的
這個(gè)工具能直接關(guān)聯(lián)到文件名,比 biosnoop 更容易定位到具體是哪個(gè)文件的 IO 有問題。
十一、IO 排查流程總結(jié)
11.1 排查決策樹
系統(tǒng)卡頓/業(yè)務(wù)超時(shí) | v top 看 %wa (iowait) ──── 低 → 不是 IO 問題,排查 CPU/內(nèi)存/網(wǎng)絡(luò) | 高 v iostat -xmt 1 ──── await 正常 → 可能是應(yīng)用層阻塞,不是磁盤瓶頸 | await 高 v 判斷設(shè)備類型 ──── NVMe → 看 await + IOPS,忽略 %util | HDD → %util + await + aqu-sz 綜合判斷 v iotop -oP ──── 找到 IO 大戶進(jìn)程 | v 分析 IO 模式 ──── wareq-sz/rareq-sz 小(4-8KB) → 隨機(jī) IO | wareq-sz/rareq-sz 大(128K+) → 順序 IO v 深入分析 ──── blktrace+btt 看各階段延遲 | bpftrace 看延遲分布和長尾 | fio 做基準(zhǔn)對(duì)比 v 調(diào)優(yōu)/解決 ──── 調(diào)度器/readahead/dirty ratio/cgroup 限制/硬件升級(jí)
11.2 工具速查表
| 工具 | 用途 | 關(guān)鍵命令 | 開銷 |
|---|---|---|---|
| top | 看 iowait 占比 | top -d 1 ,按 1 展開核心 | 極低 |
| iostat | 磁盤整體 IOPS/延遲/吞吐 | iostat -xmt 1 | 極低 |
| sar | IO 歷史趨勢(shì)回溯 | sar -dp -s 02:00 -e 04:00 | 無(讀歷史數(shù)據(jù)) |
| iotop | 定位 IO 密集進(jìn)程 | sudo iotop -oP | 低 |
| pidstat | 進(jìn)程級(jí) IO 統(tǒng)計(jì)(可腳本化) | pidstat -d 1 10 | 極低 |
| ionice | 調(diào)整進(jìn)程 IO 優(yōu)先級(jí) |
ionice -c 3 -p |
無 |
| blktrace | 塊層 IO 請(qǐng)求全生命周期追蹤 | blktrace -d /dev/sda -w 10 | 中(生成大量數(shù)據(jù)) |
| btt | blktrace 數(shù)據(jù)統(tǒng)計(jì)分析 | btt -i trace.bin | 無(離線分析) |
| fio | 磁盤基準(zhǔn)性能測(cè)試 | 見第六章各場(chǎng)景命令 | 高(壓測(cè)工具) |
| biolatency | IO 延遲直方圖分布 | sudo biolatency-bpfcc -D 1 | 低 |
| biosnoop | 逐條 IO 請(qǐng)求追蹤 | sudo biosnoop-bpfcc -d sda | 中 |
| ext4slower | 文件系統(tǒng)級(jí)慢操作追蹤 | sudo ext4slower-bpfcc 1 | 低 |
11.3 調(diào)優(yōu)參數(shù)速查
| 調(diào)優(yōu)項(xiàng) | 參數(shù)/文件 | 推薦值 | 適用場(chǎng)景 |
|---|---|---|---|
| IO 調(diào)度器 | /sys/block/*/queue/scheduler | NVMe:none,HDD:mq-deadline | 所有場(chǎng)景 |
| 預(yù)讀大小 | blockdev --setra | 順序讀: 2048+,隨機(jī)讀: 64 | Kafka/HDFS vs 數(shù)據(jù)庫 |
| 臟頁硬上限 | vm.dirty_ratio | 數(shù)據(jù)庫: 5,日志: 40 | 寫入密集場(chǎng)景 |
| 臟頁軟上限 | vm.dirty_background_ratio | 數(shù)據(jù)庫: 2,日志: 20 | 寫入密集場(chǎng)景 |
| 臟頁過期 | vm.dirty_expire_centisecs | 數(shù)據(jù)庫: 1000,日志: 6000 | 寫入密集場(chǎng)景 |
| 隊(duì)列深度 | /sys/block/*/queue/nr_requests | 高并發(fā): 1024,默認(rèn): 256 | 高 IOPS 場(chǎng)景 |
| IO 合并 | /sys/block/*/queue/nomerges | 隨機(jī) IO: 2,順序 IO: 0 | 純隨機(jī) IO 場(chǎng)景 |
| 文件系統(tǒng) | mount options | noatime 必開 | 所有場(chǎng)景 |
十二、總結(jié)
12.1 技術(shù)要點(diǎn)回顧
IO 棧認(rèn)知層面:
分層排查是基本功:一個(gè) IO 請(qǐng)求從 VFS 到文件系統(tǒng)、Block Layer、設(shè)備驅(qū)動(dòng)、物理設(shè)備,至少經(jīng)過五層。排查 IO 問題的核心方法論就是逐層定位,而不是上來就調(diào)內(nèi)核參數(shù)碰運(yùn)氣。搞清楚瓶頸在軟件層(Q2D)還是硬件層(D2C),后續(xù)的調(diào)優(yōu)方向完全不同
%util的適用邊界必須搞清楚:這是生產(chǎn)環(huán)境中最高頻的認(rèn)知誤區(qū)。HDD 是單通道設(shè)備,%util100% 確實(shí)意味著飽和;但 SSD 內(nèi)部有大量并行通道,%util100% 可能只用了實(shí)際能力的 10%。NVMe 設(shè)備判斷是否飽和,看await和實(shí)際 IOPS 與標(biāo)稱值的差距,%util直接忽略
await是延遲排查的錨點(diǎn):它包含隊(duì)列等待和設(shè)備服務(wù)兩部分時(shí)間。HDD 正常 5-15ms,SATA SSD 正常 0.05-0.5ms,NVMe 正常 0.01-0.1ms。超出正常范圍就要往下查,低于正常范圍說明磁盤還有余量
svctm已廢棄,不要再用:sysstat 12.x 明確標(biāo)記該字段不可靠,它是用%util反算出來的,在 blk-mq 多隊(duì)列架構(gòu)下完全失真
工具鏈層面:
iostat → iotop → blktrace → bpftrace,四級(jí)遞進(jìn):iostat 回答"磁盤整體忙不忙",iotop 回答"誰在讀寫",blktrace 回答"IO 請(qǐng)求在各階段花了多少時(shí)間",bpftrace 回答"延遲分布長什么樣、長尾在哪里"。每個(gè)工具解決一個(gè)層面的問題,不要指望一個(gè)工具搞定所有事
fio 基準(zhǔn)測(cè)試是調(diào)優(yōu)的前提:不做基準(zhǔn)就調(diào)參數(shù)等于盲調(diào)。先用 fio 的io_uring引擎跑出磁盤在 4K 隨機(jī)讀寫、128K 順序讀寫下的 IOPS 和吞吐上限,再拿業(yè)務(wù)實(shí)際負(fù)載的 iostat 數(shù)據(jù)去對(duì)比,才能判斷是磁盤能力不足還是應(yīng)用 IO 模式有問題
調(diào)優(yōu)參數(shù)層面:
IO 調(diào)度器選型直接影響性能:NVMe 用none(硬件自帶調(diào)度,軟件層不要畫蛇添足),HDD 用mq-deadline(保證讀延遲可控),多租戶共享 HDD 用bfq(按進(jìn)程公平分配帶寬)。用 udev 規(guī)則持久化,不要每次重啟后手動(dòng)設(shè)置
臟頁參數(shù)是寫入延遲毛刺的主因:數(shù)據(jù)庫場(chǎng)景把dirty_ratio調(diào)到 5%、dirty_background_ratio調(diào)到 2%,可以顯著減少突發(fā)刷盤導(dǎo)致的延遲抖動(dòng)。這是最常被忽略但效果最明顯的調(diào)優(yōu)項(xiàng)
readahead 要按場(chǎng)景區(qū)分:Kafka、HDFS 這類順序讀密集的場(chǎng)景調(diào)大到 1MB 以上;MySQL OLTP 這類純隨機(jī)讀場(chǎng)景調(diào)小到 32KB,避免預(yù)讀浪費(fèi)帶寬
cgroup v2 的 io 控制器是多租戶環(huán)境的剛需:io.max做硬限制、io.latency做延遲保障、io.weight做權(quán)重分配,三者配合使用可以在不升級(jí)硬件的前提下解決 IO 爭搶問題
六、總結(jié)
6.2 進(jìn)階學(xué)習(xí)方向
eBPF/bpftrace 存儲(chǔ)觀測(cè)體系:本文用到的 biolatency、biosnoop 只是 bcc-tools 的預(yù)置腳本,實(shí)際生產(chǎn)中經(jīng)常需要自定義追蹤邏輯。bpftrace 支持掛載到block_rq_issue、block_rq_complete等 tracepoint 上,按進(jìn)程、按設(shè)備、按 IO 大小做多維度延遲分布統(tǒng)計(jì)。更進(jìn)一步,可以用 libbpf + CO-RE 編寫常駐的 IO 觀測(cè) daemon,替代 blktrace 實(shí)現(xiàn)低開銷的長期追蹤。Brendan Gregg 的 bpftrace 工具集和 libbpf-bootstrap 項(xiàng)目是兩個(gè)值得深入研究的起點(diǎn)
io_uring 異步 IO 框架:io_uring 通過用戶態(tài)和內(nèi)核態(tài)共享的 SQ(提交隊(duì)列)/CQ(完成隊(duì)列)環(huán)形緩沖區(qū),將系統(tǒng)調(diào)用開銷降到接近零。在高 IOPS 場(chǎng)景下比 libaio 高出 10%-30% 的性能。值得關(guān)注的高級(jí)特性包括:固定緩沖區(qū)(fixed buffers)避免每次 IO 的內(nèi)存注冊(cè)開銷、鏈?zhǔn)教峤唬╨inked SQEs)實(shí)現(xiàn)原子性多步操作、多環(huán)共享(IORING_SETUP_ATTACH_WQ)減少內(nèi)核線程數(shù)。PostgreSQL 16+、RocksDB、SPDK 已經(jīng)原生集成 io_uring,理解其工作機(jī)制對(duì)評(píng)估和調(diào)優(yōu)這些系統(tǒng)的 IO 性能有直接幫助
NVMe 深度優(yōu)化:NVMe 設(shè)備的性能調(diào)優(yōu)遠(yuǎn)不止選個(gè)none調(diào)度器。硬件多隊(duì)列到 CPU 核心的映射關(guān)系(通過/proc/interrupts和smp_affinity查看和調(diào)整)在 NUMA 架構(gòu)下對(duì)延遲影響顯著——跨 NUMA 節(jié)點(diǎn)訪問 NVMe 隊(duì)列會(huì)增加 30%-50% 的延遲。此外,NVMe 的 namespace 管理、SR-IOV 虛擬化直通、CMB(Controller Memory Buffer)、ZNS(Zoned Namespace)等特性在大規(guī)模存儲(chǔ)集群中逐漸落地,nvme-cli工具集是管理和診斷 NVMe 設(shè)備的必備技能
存儲(chǔ)棧全鏈路可觀測(cè)性建設(shè):把 iostat/sar 的指標(biāo)接入 Prometheus(通過 node_exporter 的 diskstats collector),用 Grafana 構(gòu)建磁盤 IO 大盤,配合本文提到的告警規(guī)則(基于 await 而非 %util),形成從指標(biāo)采集、異常告警到根因定位的完整閉環(huán)。對(duì)于 Kubernetes 環(huán)境,還需要關(guān)注 CSI 驅(qū)動(dòng)層面的 IO 指標(biāo)和 PV/PVC 級(jí)別的 IO 隔離
12.3 參考資料
Linux Block IO Layer- 內(nèi)核官方塊層文檔,理解 blk-mq 架構(gòu)的權(quán)威來源
iostat(1) man page - sysstat- iostat 各字段的精確定義,排查時(shí)遇到指標(biāo)含義不確定直接查這里
BPF Performance Tools (Brendan Gregg)- eBPF 性能工具的系統(tǒng)性參考書,第九章專門講磁盤 IO 追蹤
fio Documentation- fio 官方文檔,參數(shù)說明最全,做基準(zhǔn)測(cè)試前必讀
Linux Storage Stack Diagram- Linux 存儲(chǔ)??梢暬皥D,每個(gè)內(nèi)核版本都有對(duì)應(yīng)的更新版本
io_uring 內(nèi)核文檔- io_uring 的內(nèi)核側(cè)官方文檔,涵蓋 API 設(shè)計(jì)和使用約束
liburing GitHub- io_uring 的用戶態(tài)封裝庫,Jens Axboe 維護(hù),示例代碼是學(xué)習(xí) io_uring 編程的最佳入口
bcc/libbpf-tools- biolatency、biosnoop 等工具的 libbpf 版本源碼,比 Python 版性能更好
NVMe CLI- NVMe 設(shè)備管理和診斷的命令行工具集,支持 SMART 信息查看、固件更新、namespace 管理
Systems Performance, 2nd Edition (Brendan Gregg)- 系統(tǒng)性能分析的經(jīng)典著作,磁盤 IO 章節(jié)覆蓋了從原理到工具的完整知識(shí)體系
-
內(nèi)存
+關(guān)注
關(guān)注
9文章
3210瀏覽量
76369 -
磁盤
+關(guān)注
關(guān)注
1文章
398瀏覽量
26474 -
數(shù)據(jù)庫
+關(guān)注
關(guān)注
7文章
4020瀏覽量
68349
原文標(biāo)題:磁盤 IO 飆升導(dǎo)致系統(tǒng)卡死?iostat 與 iotop 深度實(shí)戰(zhàn)
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
HarmonyOS AI輔助編程工具(CodeGenie)智慧調(diào)優(yōu)
HarmonyOSAI編程智慧調(diào)優(yōu)
基于全HDD aarch64服務(wù)器的Ceph性能調(diào)優(yōu)實(shí)踐總結(jié)
如何對(duì)電機(jī)進(jìn)行調(diào)優(yōu)?調(diào)優(yōu)的好處是什么?
Linux用電功耗調(diào)優(yōu)的筆記分享
關(guān)于JVM的調(diào)優(yōu)知識(shí)
KeenOpt調(diào)優(yōu)算法框架實(shí)現(xiàn)對(duì)調(diào)優(yōu)對(duì)象和配套工具的快速適配
jvm調(diào)優(yōu)主要是調(diào)哪里
jvm調(diào)優(yōu)工具有哪些
鴻蒙開發(fā)實(shí)戰(zhàn):【性能調(diào)優(yōu)組件】
深度解析JVM調(diào)優(yōu)實(shí)踐應(yīng)用
Linux磁盤IO詳細(xì)解析
MMC SW調(diào)優(yōu)算法
MMC DLL調(diào)優(yōu)
磁盤IO問題的定位根因與調(diào)優(yōu)解決思路
評(píng)論