磁盤I/O性能優化的幾個思路

磁盤I/O性能優化的幾個思路

本文已收錄GitHub,更有互聯網大廠面試真題,面試攻略,高效學習資料等ios

雖然 I/O 的性能指標不少,相應的性能分析工具也有好幾個,但理解了各類指標的含義後,你就會發現它們其實都有必定的關聯。git

順着這些關係往下理解,你就會發現,掌握這些經常使用的瓶頸分析思路,其實並不難。github

找出了 I/O 的性能瓶頸後,下一步要作的就是優化了,也就是如何以最快的速度完成 I/O操做,或者換個思路,減小甚至避免磁盤的 I/O 操做。面試

本文,我就來講說,優化 I/O 性能問題的思路和注意事項。算法

I/O基準測試

按照個人習慣,優化以前,我會先問本身, I/O 性能優化的目標是什麼?換句話說,咱們觀察的這些 I/O 性能指標(好比 IOPS、吞吐量、延遲等),要達到多少才合適呢?數據庫

事實上,I/O 性能指標的具體標準,每一個人估計會有不一樣的答案,由於咱們每一個人的應用場景、使用的文件系統和物理磁盤等,都有可能不同。緩存

爲了更客觀合理地評估優化效果,咱們首先應該對磁盤和文件系統進行基準測試,獲得文件系統或者磁盤 I/O 的極限性能。性能優化

fio(Flexible I/O Tester)正是最經常使用的文件系統和磁盤 I/O 性能基準測試工具。它提供了大量的可定製化選項,能夠用來測試,裸盤或者文件系統在各類場景下的 I/O 性能,包括了不一樣塊大小、不一樣 I/O 引擎以及是否使用緩存等場景。網絡

fio 的安裝比較簡單,你能夠執行下面的命令來安裝它:異步

# Ubuntu
apt-get install -y fio
# CentOS
yum install -y fio

安裝完成後,就能夠執行 man fio 查詢它的使用方法。

fio 的選項很是多, 我會經過幾個常見場景的測試方法,介紹一些最經常使用的選項。這些常見場景包括隨機讀、隨機寫、順序讀以及順序寫等,你能夠執行下面這些命令來測試:

#隨機讀
fio -name=randread -direct=1 -iodepth=64 -rw=randread -ioengine=libaio -bs=4k -size=1G
#隨機寫
fio -name=randwrite -direct=1 -iodepth=64 -rw=randwrite -ioengine=libaio -bs=4k -size=1G
#順序讀
fio -name=read -direct=1 -iodepth=64 -rw=read -ioengine=libaio -bs=4k -size=1G -numjobs=
#順序寫
fio -name=write -direct=1 -iodepth=64 -rw=write -ioengine=libaio -bs=4k -size=1G -numjob

在這其中,有幾個參數須要你重點關注一下。

  • direct,表示是否跳過系統緩存。上面示例中,我設置的 1 ,就表示跳過系統緩存。
  • iodepth,表示使用異步 I/O(asynchronous I/O,簡稱 AIO)時,同時發出的 I/O 請求上限。在上面的示例中,我設置的是 64。
  • rw,表示 I/O 模式。個人示例中, read/write 分別表示順序讀 / 寫,而randread/randwrite 則分別表示隨機讀 / 寫。
  • ioengine,表示 I/O 引擎,它支持同步(sync)、異步(libaio)、內存映射(mmap)、網絡(net)等各類 I/O 引擎。上面示例中,我設置的 libaio 表示使用異步I/O。
  • bs,表示 I/O 的大小。示例中,我設置成了 4K(這也是默認值)。
  • filename,表示文件路徑,固然,它能夠是磁盤路徑(測試磁盤性能),也能夠是文件路徑(測試文件系統性能)。示例中,我把它設置成了磁盤 /dev/sdb。不過注意,用磁盤路徑測試寫,會破壞這個磁盤中的文件系統,因此在使用前,你必定要事先作好數據備份。

下面就是我使用 fio 測試順序讀的一個報告示例。

read: (g=0): rw=read, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libfio-3.1
Starting 1 process
Jobs: 1 (f=1): [R(1)][100.0%][r=16.7MiB/s,w=0KiB/s][r=4280,w=0 IOPS][eta 00m:00s]
read: (groupid=0, jobs=1): err= 0: pid=17966: Sun Dec 30 08:31:48 2018
read: IOPS=4257, BW=16.6MiB/s (17.4MB/s)(1024MiB/61568msec)
slat (usec): min=2, max=2566, avg= 4.29, stdev=21.76
clat (usec): min=228, max=407360, avg=15024.30, stdev=20524.39
lat (usec): min=243, max=407363, avg=15029.12, stdev=20524.26
clat percentiles (usec):
|  1.00th=[   498],  5.00th=[  1020], 10.00th=[  1319], 20.00th=[  1713],
| 30.00th=[  1991], 40.00th=[  2212], 50.00th=[  2540], 60.00th=[  2933],
  | 70.00th=[  5407], 80.00th=[ 44303], 90.00th=[ 45351], 95.00th=[ 45876],
| 99.00th=[ 46924], 99.50th=[ 46924], 99.90th=[ 48497], 99.95th=[ 49021],
| 99.99th=[404751]
bw (  KiB/s): min= 8208, max=18832, per=99.85%, avg=17005.35, stdev=998.94, samples=1iops        : min= 2052, max= 4708, avg=4251.30, stdev=249.74, samples=123
lat (usec)   : 250=0.01%, 500=1.03%, 750=1.69%, 1000=2.07%
lat (msec)   : 2=25.64%, 4=37.58%, 10=2.08%, 20=0.02%, 50=29.86%
lat (msec)   : 100=0.01%, 500=0.02%
cpu          : usr=1.02%, sys=2.97%, ctx=33312, majf=0, minf=75
IO depths    : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
submit    : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
complete  : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
issued rwt: total=262144,0,0, short=0,0,0, dropped=0,0,0
latency   : target=0, window=0, percentile=100.00%, depth=64
Run status group 0 (all jobs):
READ: bw=16.6MiB/s (17.4MB/s), 16.6MiB/s-16.6MiB/s (17.4MB/s-17.4MB/s), io=1024MiB (1
Disk stats (read/write):
sdb: ios=261897/0, merge=0/0, ticks=3912108/0, in_queue=3474336, util=90.09%

這個報告中,須要咱們重點關注的是, slat、clat、lat ,以及 bw 和 iops 這幾行。

先來看剛剛提到的前三個參數。事實上,slat、clat、lat 都是指 I/O 延遲(latency)。不一樣之處在於:

  • slat ,是指從 I/O 提交到實際執行 I/O 的時長(Submission latency);
  • clat ,是指從 I/O 提交到 I/O 完成的時長(Completion latency);
  • 而 lat ,指的是從 fio 建立 I/O 到 I/O 完成的總時長。

這裏須要注意的是,對同步 I/O 來講,因爲 I/O 提交和 I/O 完成是一個動做,因此 slat 實際上就是 I/O 完成的時間,而 clat 是 0。而從示例能夠看到,使用異步 I/O(libaio)時,lat 近似等於 slat + clat 之和。

再來看 bw ,它表明吞吐量。在我上面的示例中,你能夠看到,平均吞吐量大約是 16MB(17005 KiB/1024)。

最後的 iops ,其實就是每秒 I/O 的次數,上面示例中的平均 IOPS 爲 4250。

一般狀況下,應用程序的 I/O 都是讀寫並行的,並且每次的 I/O 大小也不必定相同。因此,剛剛說的這幾種場景,並不能精確模擬應用程序的 I/O 模式。那怎麼才能精確模擬應用程序的 I/O 模式呢?

幸運的是,fio 支持 I/O 的重放。藉助前面提到過的 blktrace,再配合上 fio,就能夠實現對應用程序 I/O 模式的基準測試。你須要先用 blktrace ,記錄磁盤設備的 I/O 訪問狀況;而後使用 fio ,重放 blktrace 的記錄。

好比你能夠運行下面的命令來操做:

#使用blktrace跟蹤磁盤I/O,注意指定應用程序正在操做的磁盤
$ blktrace /dev/sdb
#查看blktrace記錄的結果
# ls
sdb.blktrace.0  sdb.blktrace.1
#將結果轉化爲二進制文件
$ blkparse sdb -d sdb.bin
#使用fio重放日誌
$ fio --name=replay --filename=/dev/sdb --direct=1 --read_iolog=sdb.bin

這樣,咱們就經過 blktrace+fio 的組合使用,獲得了應用程序 I/O 模式的基準測試報告。

I/O性能優化

獲得 I/O 基準測試報告後,再用上咱們上一節總結的性能分析套路,找出 I/O 的性能瓶頸並優化,就是水到渠成的事情了。固然,想要優化 I/O 性能,確定離不開 Linux 系統的I/O 棧圖的思路輔助。你能夠結合下面的 I/O 棧圖再回顧一下。

磁盤I/O性能優化的幾個思路

下面,我就帶你從應用程序、文件系統以及磁盤角度,分別看看 I/O 性能優化的基本思路。

應用程序優化

首先,咱們來看一下,從應用程序的角度有哪些優化 I/O 的思路。

應用程序處於整個 I/O 棧的最上端,它能夠經過系統調用,來調整 I/O 模式(如順序仍是隨機、同步仍是異步), 同時,它也是 I/O 數據的最終來源。在我看來,能夠有這麼幾種方式來優化應用程序的 I/O 性能。

第一,能夠用追加寫代替隨機寫,減小尋址開銷,加快 I/O 寫的速度。

第二,能夠藉助緩存 I/O ,充分利用系統緩存,下降實際 I/O 的次數。

第三,能夠在應用程序內部構建本身的緩存,或者用 Redis 這類外部緩存系統。這樣,一方面,能在應用程序內部,控制緩存的數據和生命週期;另外一方面,也能下降其餘應用程序使用緩存對自身的影響。

好比,在前面的 MySQL 案例中,咱們已經見識過,只是由於一個干擾應用清理了系統緩存,就會致使 MySQL 查詢有數百倍的性能差距(0.1s vs 15s)。

再如, C 標準庫提供的 fopen、fread 等庫函數,都會利用標準庫的緩存,減小磁盤的操做。而你直接使用 open、read 等系統調用時,就只能利用操做系統提供的頁緩存和緩衝區等,而沒有庫函數的緩存可用。

第四,在須要頻繁讀寫同一塊磁盤空間時,能夠用 mmap 代替 read/write,減小內存的拷貝次數。

第五,在須要同步寫的場景中,儘可能將寫請求合併,而不是讓每一個請求都同步寫入磁盤,便可以用 fsync() 取代 O_SYNC。

第六,在多個應用程序共享相同磁盤時,爲了保證 I/O 不被某個應用徹底佔用,推薦你使用 cgroups 的 I/O 子系統,來限制進程 / 進程組的 IOPS 以及吞吐量。

最後,在使用 CFQ 調度器時,能夠用 ionice 來調整進程的 I/O 調度優先級,特別是提升核心應用的 I/O 優先級。ionice 支持三個優先級類:Idle、Best-effort 和 Realtime。其中, Best-effort 和 Realtime 還分別支持 0-7 的級別,數值越小,則表示優先級別越高。

文件系統優化

應用程序訪問普通文件時,實際是由文件系統間接負責,文件在磁盤中的讀寫。因此,跟文件系統中相關的也有不少優化 I/O 性能的方式。

第一,你能夠根據實際負載場景的不一樣,選擇最適合的文件系統。好比 Ubuntu 默認使用ext4 文件系統,而 CentOS 7 默認使用 xfs 文件系統。

相比於 ext4 ,xfs 支持更大的磁盤分區和更大的文件數量,如 xfs 支持大於 16TB 的磁盤。可是 xfs 文件系統的缺點在於沒法收縮,而 ext4 則能夠。

第二,在選好文件系統後,還能夠進一步優化文件系統的配置選項,包括文件系統的特性(如 ext_attr、dir_index)、日誌模式(如 journal、ordered、writeback)、掛載選項(如 noatime)等等。

好比,使用 tune2fs 這個工具,能夠調整文件系統的特性(tune2fs 也經常使用來查看文件系統超級塊的內容)。 而經過 /etc/fstab ,或者 mount 命令行參數,咱們能夠調整文件系統的日誌模式和掛載選項等。

第三,能夠優化文件系統的緩存。

好比,你能夠優化 pdflush 髒頁的刷新頻率(好比設置 dirty_expire_centisecs 和dirty_writeback_centisecs)以及髒頁的限額(好比調整 dirty_background_ratio 和dirty_ratio 等)。

再如,你還能夠優化內核回收目錄項緩存和索引節點緩存的傾向,即調整vfs_cache_pressure(/proc/sys/vm/vfs_cache_pressure,默認值 100),數值越大,就表示越容易回收。

最後,在不須要持久化時,你還能夠用內存文件系統tmpfs,以得到更好的 I/O 性能 。tmpfs 把數據直接保存在內存中,而不是磁盤中。好比 /dev/shm/ ,就是大多數 Linux 默認配置的一個內存文件系統,它的大小默認爲總內存的一半。

磁盤優化

數據的持久化存儲,最終仍是要落到具體的物理磁盤中,同時,磁盤也是整個 I/O 棧的最底層。從磁盤角度出發,天然也有不少有效的性能優化方法。

第一,最簡單有效的優化方法,就是換用性能更好的磁盤,好比用 SSD 替代 HDD。

第二,咱們可使用 RAID ,把多塊磁盤組合成一個邏輯磁盤,構成冗餘獨立磁盤陣列。這樣作既能夠提升數據的可靠性,又能夠提高數據的訪問性能。

第三,針對磁盤和應用程序 I/O 模式的特徵,咱們能夠選擇最適合的 I/O 調度算法。比方說,SSD 和虛擬機中的磁盤,一般用的是 noop 調度算法。而數據庫應用,我更推薦使用deadline 算法。

第四,咱們能夠對應用程序的數據,進行磁盤級別的隔離。好比,咱們能夠爲日誌、數據庫等 I/O 壓力比較重的應用,配置單獨的磁盤。

第五,在順序讀比較多的場景中,咱們能夠增大磁盤的預讀數據,好比,你能夠經過下面兩種方法,調整 /dev/sdb 的預讀大小。

調整內核選項 /sys/block/sdb/queue/read_ahead_kb,默認大小是 128 KB,單位爲KB。
使用 blockdev 工具設置,好比 blockdev --setra 8192 /dev/sdb,注意這裏的單位是512B(0.5KB),因此它的數值老是 read_ahead_kb 的兩倍。

第六,咱們能夠優化內核塊設備 I/O 的選項。好比,能夠調整磁盤隊列的長度/sys/block/sdb/queue/nr_requests,適當增大隊列長度,能夠提高磁盤的吞吐量(固然也會致使 I/O 延遲增大)。

最後,要注意,磁盤自己出現硬件錯誤,也會致使 I/O 性能急劇降低,因此發現磁盤性能急劇降低時,你還須要確認,磁盤自己是否是出現了硬件錯誤。

好比,你能夠查看 dmesg 中是否有硬件 I/O 故障的日誌。還可使用 badblocks、smartctl 等工具,檢測磁盤的硬件問題,或用 e2fsck 等來檢測文件系統的錯誤。若是發現問題,你可使用 fsck 等工具來修復。

總結

本文,咱們一塊兒梳理了常見的文件系統和磁盤 I/O 的性能優化思路和方法。發現 I/O 性能問題後,不要急於動手優化,而要先找出最重要的、能夠最大程度提高性能的問題,而後再從 I/O 棧的不一樣層入手,考慮具體的優化方法。

記住,磁盤和文件系統的 I/O ,一般是整個系統中最慢的一個模塊。因此,在優化 I/O 問題時,除了能夠優化 I/O 的執行流程,還能夠藉助更快的內存、網絡、CPU 等,減小 I/O調用。

好比,你能夠充分利用系統提供的 Buffer、Cache ,或是應用程序內部緩存, 再或者Redis 這類的外部緩存系統。

相關文章
相關標籤/搜索