1. 程式人生 > 其它 >磁碟 IO

磁碟 IO

常見的磁碟型別

按儲存原理的不同,可以把磁碟分為這麼幾種

  1. HDD 盤:沒啥說的,就是平時最常見的機械盤。
  2. SSD 盤:用電訊號來記錄儲存資料,而不是磁片。顯然進行 I/O 時,這要比機械盤的物理定址方式快的多。
  3. HHD 盤:HDD + SSD 的組合形式。
衡量磁碟效能的指標有哪些?

有時我們發現執行在 Linux 伺服器上的某個應用響應很慢,你也不知道是什麼原因,但是就是想看看磁碟 I/O 是不是有問題。除了 I/O 還有沒有其他的指標參考呢? 答案是 有!

  1. 吞吐量: 指的是磁碟每秒處理 I/O 請求總的資料大小。用 TPS 表示,例如 32M/s。
  2. IOPS: 每秒處理 I/O 請求的次數。一般我們在說 IOPS 的時候說的就是磁碟的隨機讀寫效能,這個指標在購買雲伺服器的時候,廠商會明確的告訴你該資料,用來區分不同雲存屬性的效能。
  3. 使用率: 磁碟處理 I/O 的時間百分比。過高的使用率(80%+)意味著磁碟的 I/O 存在效能瓶頸。
  4. 響應時間: 通過系統呼叫核心發出 I/O 請求,到核心收到 I/O 響應花費的時間,包括 I/O 請求處於 I/O 佇列中等待的時間。

在查詢磁碟瓶頸時,這些指標是我們需要特別關注的。那麼,這些指標怎麼看?下面我用一個案例說下。

環境準備
4G , 4c 虛擬機器兩臺,安裝了 docker 環境。
S: 10.10.3.56
C: 10.10.3.55

S:
# 下載 redis 官方映象
[root@a ~]# docker pull redis

# 這裡我本地 /data 目錄下有一個自定義的 redis.conf 配置檔案,啟動時mount 到了容器的 /data 目錄
[root@a ~]# docker run -itd --name redis -p 6379:6379 -v /data:/data docker.io/redis redis-server /data/redis.conf

在另一臺虛擬機器寫指令碼程式向 redis 插入資料。

C:
[root@huiyoujia ~]# ls  # txt 是我構造的 redis 資料檔案,sh 指令碼讀取並插入到對端 Redis 例項
redis_data.txt  request.sh
[root@huiyoujia ~]# sh redis_data.txt >>/dev/null
啟動指令碼模擬應用程式 I/O 請求

排查問題的時候,我們把模擬環境的資訊忘掉。所有都是未知的,然後一步步進行排查。

一. 從應用的角度解決磁碟 I/O 瓶頸

1. 先 top 一下看看程序資訊
[root@a ~]# top
top - 17:55:01 up 36 days,  6:34,  1 user,  load average: 2.55, 0.13, 0.05
Tasks: 104 total,   1 running,  53 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.7 sy,  0.0 ni, 15.3 id, 83.7 wa,  0.0 hi,  0.3 si,  0.0 st
%Cpu1  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  0.3 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  1.3 us,  2.3 sy,  0.0 ni, 91.7 id,  0.0 wa,  0.0 hi,  4.7 si,  0.0 st
KiB Mem :  4039612 total,  3391044 free,   307880 used,   340688 buff/cache
KiB Swap:  2097148 total,  2088688 free,     8460 used.  3422408 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                 
 5385 systemd+  20   0  207616  18496   3344 D  13.3  0.5   0:14.30 redis-server                                                                           
    1 root      20   0   46192   7184   4600 S   0.0  0.2   0:30.39 systemd                                                                                    
    2 root      20   0       0      0      0 S   0.0  0.0   0:00.44 kthreadd                                                                              
    3 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 rcu_gp                                                                              
    4 root       0 -20       0      0      0 I   0.0  0.0   0:00.00 rcu_par_gp

# top 之後按快捷鍵 1 檢視所有 cpu 的使用情況。

top 輸出的資訊很多,排查 I/O 問題時這些需要重點關注:  
第一行的  load average  平均負載  
第三行的  %Cpu(s)
  us 使用者態:該值高的時候要排查使用者態的程序;
  sy 核心態: 該值高的時候,排查核心態的程序活動,cpu 上下文切換等的情況。
  id cpu空閒時間百分比:該值很高,說明你的 cpu 沒有什麼計算任務,沒事幹。
  wa cpu等待時間百分比:該值很高,說明 cpu 在等待程序準備就緒花了很多時間,大量程序如果在等待IO,則該值會很高。
第六行 
  S: 程序的狀態,關注 r 狀態的程序
  %CPU %MEM 等標籤

檢視 top 輸出資訊,看到 Cpu0 編號的 wa 值 比較高,達到了 83.7%, 而其他 cpu 則正常,有點可疑。 再接著看 程序的 cpu 時間,發現 除了 pid 是 5385 的程序 cpu 時間有點高之外,13.3 好像也不是很高,其他程序都很正常。這裡先做個標記, pid 是 5385 的程序 使用的是 redis-server 命令,有可能是 redis-server 引起的 wa 值升高。

(記住我們的初衷是:就是想看看磁碟 I/O 是不是有問題。)
現在既然懷疑是 redis-server 引起的問題,那麼就看看系統磁碟的效能狀態。

2. iostat 檢視磁碟的狀態
[root@a ~]# iostat -d -x 1
Linux 4.18.4-1.el7.elrepo.x86_64 (a.huiyoujia.com) 	08/15/2019 	_x86_64_	(4 CPU)

Device:         rrqm/s   wrqm/s     r/s     w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
scd0              0.00     0.00    0.00    0.00     0.00     0.00     0.00     0.00    0.00    0.00    0.00   0.00   0.00
sda               0.00    28.00    0.00   84.00     0.00   444.00    10.57     0.99   10.01    0.00   10.01  11.81  99.20


# -d 檢視 磁碟資訊;
# -x 列出詳細的輸出資訊;
# 每隔 1s 重新整理一次
iostat 工具除了能檢視磁碟狀態,還能檢視 cpu 等的資訊。man iostat 檢視詳細的使用方法。

回想一下前面提到的衡量磁碟效能的指標。

輸出資訊中心我們應該重點關注這些:
1. rrqm/s: 每秒合併讀的請求數
2. wrqm/s: 每秒合併寫的請求數
3. r/s: 每秒讀請求的次數
4. w/s: 每秒寫請求的次數
5. rkB/s: 每秒讀的資料大小
6. wkB/s: 每秒寫的資料大小
7. r_await: 完成讀請求等待的時間
8. w_wait: 完成寫請求等到的時間
9. %util: 磁碟的使用率
10. avgqu-sz: 磁碟 i/o 佇列的長度

# 1 & 2 在排查 mysql 資料庫時可以參考,因為 mysql 合併讀寫的情況很常見。
# 3 & 4 指的是當前磁碟的 IOPS。並不是 最大 iops。
# 5 & 6 指的就是磁碟的吞吐量
# 7 & 8 指的是響應時間
# 9 使用率

現在我們已經有了磁碟的效能指標的資料。
通過觀察發現 sda 盤的磁碟使用率已經達到了 99.2%,說明 sda 盤確實有 I/O 瓶頸。看來我們的磁碟的確有瓶頸。那麼是什麼原因或者是系統執行的什麼任務導致的 磁碟使用率升高呢? 需要 進一步分析系統程序 使用 IO 的 情況。

3. pidstat 檢視程序使用系統資源的詳細資訊
[root@a ~]# pidstat -d 2 5
Linux 4.18.4-1.el7.elrepo.x86_64 (a.huiyoujia.com) 	08/15/2019 	_x86_64_	(4 CPU)

06:59:11 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
06:59:15 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
06:59:17 PM   999      5385      0.00    118.00      0.00  redis-server
06:59:13 PM  1000     32038      0.00      1.99      0.00  mysqld
Average:        0       341      0.00      1.00      0.00  jbd2/sda2-8

06:59:17 PM   UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
06:59:19 PM   999      5385      0.00    520.00      0.00  redis-server
06:59:13 PM  1000     32038      0.00      1.99      0.00  mysqld

Average:      UID       PID   kB_rd/s   kB_wr/s kB_ccwr/s  Command
Average:      999      5385      0.00    518.35      0.00  redis-server

# -d 檢視程序的 I/O 情況
# 2 5 沒兩秒重新整理一次,重新整理五次退出

pidstat 不僅能看程序的 I/O情況, cpu ,上下文切換,記憶體等的資訊也能檢視。

pidstat 輸出的資訊中看到,pid 5383 的程序,每秒寫 500 KB 的資料,其他程序則沒有什麼 I/O 請求。pid 5383 的程序 Command 是 redis-server。 這印證了我們前面的猜測,說明我們的猜測是正確的。

好的,現在我們已經知道了是 redis-server 導致的 磁碟 I/O 瓶頸。問題結束!

但是問題並沒有解決,你的應用仍然響應很慢。還要接著查,為什麼 redis-server 會導致磁碟使用率升到呢?redis 在做什麼?

pidstat 輸出的資訊中顯示 redis-server 有大量的寫磁碟操作,讀卻很少。那麼,redis 應用 在寫什麼?

redis 執行在使用者態,根據作業系統的原理我們知道使用者態的程序發起 I/O 請求,需要經過系統呼叫,由核心程式完成 I/O 排程操作,那麼我們就要檢視 redis 應用的系統呼叫情況。

4. strace 檢視程序的系統呼叫資訊
[root@a ~]# strace -f -p 5385
[pid  5385] epoll_wait(5, [{EPOLLIN, {u32=7, u64=7}}], 10128, 42) = 1
[pid  5385] accept(7, {sa_family=AF_INET, sin_port=htons(50014), sin_addr=inet_addr("10.10.3.55")}, [16]) = 9
[pid  5385] fcntl(9, F_GETFL)           = 0x2 (flags O_RDWR)
[pid  5385] fcntl(9, F_SETFL, O_RDWR|O_NONBLOCK) = 0
[pid  5385] setsockopt(9, SOL_TCP, TCP_NODELAY, [1], 4) = 0
[pid  5385] epoll_ctl(5, EPOLL_CTL_ADD, 9, {EPOLLIN, {u32=9, u64=9}}) = 0
[pid  5385] accept(7, 0x7fff29fbec00, 0x7fff29fbebfc) = -1 EAGAIN (Resource temporarily unavailable)
[pid  5385] read(3, 0x7fff29fbecbf, 1)  = -1 EAGAIN (Resource temporarily unavailable)
[pid  5385] epoll_wait(5, [{EPOLLIN, {u32=9, u64=9}}], 10128, 40) = 1
[pid  5385] read(9, "*1\r\n$7\r\nCOMMAND\r\n", 16384) = 17
[pid  5385] read(3, 0x7fff29fbecbf, 1)  = -1 EAGAIN (Resource temporarily unavailable)
[pid  5385] write(8, "*200\r\n*6\r\n$9\r\npexpireat\r\n:3\r\n", 29) = 29
[pid  5385] write(8, "*2\r\n+write\r\n+fast\r\n:1\r\n:1\r\n:1\r\n*"..., 50) = 50
[pid  5385] write(8, "*3\r\n+write\r\n+denyoom\r\n+fast\r\n:1\r"..., 62) = 62
[pid  5385] write(8, "*2\r\n+admin\r\n+noscript\r\n:0\r\n:0\r\n:"..., 55) = 55



# -f 檢視字程序 或者 子執行緒的資訊
# -p 指定 pid。這裡我們已經知道是 5385 的程序了。

觀察一會他這個輸出基本是迴圈的,這裡擷取其中一段。(案例中是迴圈輸出,其他情況不一定,需要多觀察輸出資訊)

epoll_wait, read, write, setsockopt 等這些都是系統函式,5383 的 程序因為 一直在寫東西,
所以這裡顯示有很多 write() 函式呼叫的情況,括號裡的第一個數字指的 FD,就是檔案描述符為 9,在後面那一串看起來是換行許可權什麼的,沒什麼用。只要找到檔案描述符就可以了。

知道了redis-server 在寫 FD 號是 8 的檔案 我們能幹什麼? 能查到具體的檔案。

關於檔案描述符:
Linux 一切皆檔案,核心利用檔案描述符來訪問檔案。開啟現存檔案或新建檔案時,核心會返回一個檔案描述符。讀寫檔案也需要使用檔案描述符來指定待讀寫的檔案。每一個檔案描述符會與一個開啟檔案相對應,同時,不同的檔案描述符也會指向同一個檔案。相同的檔案可以被不同的程序開啟也可以在同一個程序中被多次開啟。系統為每一個程序維護了一個檔案描述符表,該表的值都是從0開始的,所以在不同的程序中你會看到相同的檔案描述符,這種情況下相同檔案描述符有可能指向同一個檔案,也有可能指向不同的檔案。具體情況要具體分析,要理解具體其概況如何,需要檢視由核心維護的3個數據結構。
1.程序級的檔案描述符表;
2.系統級的開啟檔案描述符表;
3.檔案系統的i-node表。

i-node 即 index-name ,inode 號是唯一的,切只能一個檔案或者目錄擁有。作業系統並不清楚檔名和目錄名,而是操作 inode 號碼,檔名和目錄名只是友好顯示, 更改檔名對作業系統無感,因為更改檔名 inode 號也不會變。如果你知道inode號,則可以用 inode 號管理這個檔案。

5. lsof 檢視程序開啟的檔案
[root@a ~]# lsof -p 5385
COMMAND    PID              USER   FD      TYPE DEVICE SIZE/OFF    NODE NAME
redis-ser 5385 systemd-bus-proxy  cwd       DIR    8,2     4096 5505025 /data
redis-ser 5385 systemd-bus-proxy  rtd       DIR   0,43     4096 5640717 /
redis-ser 5385 systemd-bus-proxy  txt       REG   0,43 10407928 5640636 /usr/local/bin/redis-server
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5640636 /usr/local/bin/redis-server (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5636195 /lib/x86_64-linux-gnu/libc-2.28.so (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5636250 /lib/x86_64-linux-gnu/libpthread-2.28.so (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5636254 /lib/x86_64-linux-gnu/librt-2.28.so (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5636203 /lib/x86_64-linux-gnu/libdl-2.28.so (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5636218 /lib/x86_64-linux-gnu/libm-2.28.so (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy  mem       REG    8,2          5636181 /lib/x86_64-linux-gnu/ld-2.28.so (stat: No such file or directory)
redis-ser 5385 systemd-bus-proxy    0u      CHR  136,2      0t0       5 /2
redis-ser 5385 systemd-bus-proxy    1u      CHR  136,2      0t0       5 /2
redis-ser 5385 systemd-bus-proxy    2u      CHR  136,2      0t0       5 /2
redis-ser 5385 systemd-bus-proxy    3r     FIFO   0,12      0t0 4723129 pipe
redis-ser 5385 systemd-bus-proxy    4w     FIFO   0,12      0t0 4723129 pipe
redis-ser 5385 systemd-bus-proxy    5u  a_inode   0,13        0    9538 [eventpoll]
redis-ser 5385 systemd-bus-proxy    6u     sock    0,9      0t0 4723130 protocol: TCPv6
redis-ser 5385 systemd-bus-proxy    7u     sock    0,9      0t0 4723131 protocol: TCP
redis-ser 5385 systemd-bus-proxy    8w      REG    8,2  6551861 5505296 /data/appendonly.aof
redis-ser 5385 systemd-bus-proxy    9u     sock    0,9      0t0 4874906 protocol: TCP

第四列最後二行 8w 是 redis-server 程序操作的 FD,與之前 strace 追蹤到的對應。8w,w 是寫的意思,REG 常規檔案,正在寫的檔案是 /data/appendonly.aof。
至此,我們找到了 redis 在寫檔案 /data/appendonly.aof,導致了磁碟 I/O 瓶頸。

## 以上輸出的資訊系統 man 手冊都能看到解釋,忘記了可以進行檢視。常用的熟悉就可以。

appendonly.aof 是 redis 的快照資料,有可能是 redis 快照策略配置的不合理,這個時候就要檢查 redis 的配置了。(案例中故意配置了不合理的快照策略)

排查系統性能問題耗時費力,案例只是提供了 排查 I/O 效能的一種思路。也有其他的方法其他工具,比如 sar 可以替換 iostat 。(不過,這個方式差不多就可以搞定絕大部分 i/o 問題了)

工具鏈:top-->iostat-->pidstat-->strace-->lsof
這些工具使用非常靈活,引數非常多,沒有必要都記住,忘記了可以檢視 man 手冊。

二. 從物理角度解決磁碟 I/O 瓶頸

如果不是應用程式的問題導致磁碟 I/O 瓶頸,確實是業務量的需求,那麼需要物理上提高磁碟的 I/O 效能。

常用的方式:

做 Raid 磁碟陣列,不同的 Raid 級別儲存效率差異較大。常見的有這些:

  1. Raid 0:條帶模式,是一種水平擴充套件方式,n 快盤連線起來,看起來像一塊盤,容量是 n 快盤的總和。儲存時把 資料打散,分散的存到道不同的盤上,由於是 多快盤同時進行 I/O,所以 Raid 0 的 IOPS 是線性的提高。但是其中一塊盤壞掉,資料就會丟失,資料冗餘較差。
  2. Raid 1:映象模式,至少需要兩塊盤才能做 Raid 1。每次把寫兩份資料,一份存到映象盤上。
    容量和IOPS 是線性下降。但是資料安全性很高,主盤快掉,映象盤可以進行替換。
  3. Raid 10: 是 Raid 1 和 Raid 0 的組合方式,至少需要 4 快磁碟,每兩塊先做出成 Raid 1,然後再把 Raid 1 做成 Raid 0 。及解決了 Raid 0 資料不安全的問題,又解決了 Raid 1 IOPS 效能的問題。
  4. Raid 5:奇偶校驗儲存,至少需要三塊磁碟。資料儲存用奇偶校驗的方式存到不同的盤上,當有磁碟損壞是,用奇偶儲存推算出丟失的資料進行恢復。是Raid 0 和 Raid 1 的這種方案,IOPS 介於兩者之間。

不同級別可以任意組合,像 Raid10 + Raid10 再組合成 Raid 0

IOPS: Raid 0 > Raid 10 ≈ Raid 5 > 單塊磁碟 > Raid 1

mysql 資料庫伺服器,通常會使用 Raid 10 方式。日誌伺服器 Raid 0。
磁碟陣列是由 Raid 控制卡(硬體)完成的,Raid 控制卡包含了一塊512M的記憶體,有讀寫快取的能力,但是寫快取預設是未開啟的,開啟後能有效的提高磁碟的 IOPS 效能。

使用 SSD 盤

IOPS 作為衡量磁碟 I/O 效能的重要指標,是我們需要重點關注的。
說 ssd 盤速度快,一般指的是 IOPS 效能的提升。

如何採購硬碟?

1. 機械盤:

機械盤要關注磁碟轉速引數,轉速 10k, 表示 每分鐘磁碟轉 1w 圈。由機械盤的儲存原理可知,轉速越快意味著旋轉延遲越小,磁針能更快的到達指定扇區。
伺服器磁碟轉速 10k, 15k 的比較常見。PC 機磁碟 5400, 7200。

用轉速估算磁碟的 IOPS:
IOPS = 1s/(尋道時間 + 旋轉延遲)ms

尋道時間:磁碟上有不同半徑的磁軌,磁軌上分佈著各個扇區,扇區是磁碟讀寫資料的最小單元。讀寫磁碟上的資料,靠的是磁針在不同的磁軌上來回移動,磁軌的半徑不同,
磁針移動到不同磁軌上的時間也就不同。目前的機械盤,尋道時間在 3-15ms(平均數)    

旋轉延遲:磁針到達指定半徑的磁軌之後,還不能立刻讀寫扇區上的資料,因為同半徑的磁軌上有很多扇區,磁針要等待磁碟旋轉到達指定的扇區才行。有可能磁針剛好停留的位置
就是需要的扇區,也有可能需要磁碟旋轉一週。旋轉延通常遲取半圈旋轉的時間。  
以 15k 轉速為例: 旋轉時間 = 1/(15000/60s/1000) * 1/2 = 2ms,7200 是 4.2ms
這就是你 PC 慢的原因。

15k iops估算:
1s/(3 + 2)ms = 200 (最大值)
7.2k iops: 160

實際上磁碟使用不同的介面方式也能提高 IOPS 效能,機械盤的介面型別有,SCSI,SAS,SATA 這幾種,15k 的SATA 介面盤比 SCSI 介面的盤 IOPS 效能要好。

2. SSD 盤

SSD 盤沒有旋轉的概念,採購的時候需要問清楚廠商 IOPS 的資料。
SSD 盤按介面型別可分為 STAT ssd 和 PCIE ssd。

普通 SSD:(STAT 介面)

三星。
官網 IOPS 資訊

4KB 隨機讀取(QD32):最大 96,000 IOPS
4KB 隨機寫入(QD1):最大 42,000 IOPS

IOPS 測試要跟相同的 檔案快大小比較。4kb 檔案塊和 16kb 檔案快 測試的資料沒有可比性,不是一個概念。 檔案快越小,才能測出 IOPS 的最佳效能。
ssd 盤的 讀寫是不對稱的,不想機械盤一樣,這根 ssd 盤的儲存原理有關。

上面的 IOPS 大致能換算成隨機 MB/s 的讀寫。
比如 隨機寫 : (42000 * 4kb) /1024 = 164 Mb/s

對比 HDD 盤的 IOPS ,SSD 盤 IOPS 的提升不是線性提高,而是指數級別的提高。對於 資料庫伺服器來講,使用 SSD 儲存,可以對 DB 速度有一個質的提升。

PCIE SSD:

Intel。
廠商 IOPS 資訊

512G 容量:

4KB 隨機讀取:最大 340000 IOPS
4KB 隨機寫入:最大 275000 IOPS

PCIE ssd 在 普通 ssd 的基礎上 把 IOPS 效能 又提升了 3-5 倍。

IOPS 指的是隨機 I/O,能有效的衡量磁碟的 I/O 效能。磁碟有預讀頁或者檔案塊的機制,順序 I/O 比 隨機 I/O 要快的多,順序 I/O 通常不是我們關注的物件。
一般我們對磁碟的操作隨機 I/O 的 請求比例更多,如果在應用層把隨機 I/O 優化成 順序 I/O(或者是邏輯上的 順序 I/O), 那麼,應用的響應速度會快很多。
Mysql innodb 儲存引擎的 很多優化思想 就是這個邏輯,像索引,undo,redo 日誌記錄 等,儘可能的把 隨機 I/O 變得有序。