作者:朱小廝 來源:https://www.jianshu.com/p/76ca793daf1d
一、概述
本篇起源于對Kafka的一個問題排查,大致的原因是達到磁盤性能瓶頸。在追蹤問題的時候用到IOStat -x這命令,詳細示例如下:
可以看到%idle(%idle小于70%說明IO壓力已經比較大了)和%util的值都處于非正常狀態。不過這里并不講述Kafka的問題排查過程,反而是來講述下IO指標的一些知識。每次遇到需要查看磁盤相關信息的時候,一些指標都會或多或少的遺忘,還要翻閱各種資料了解,故這里對相關的信息做一個相關的整理,在鞏固相關知識點的同時也方便以后的查閱。
上面示例中的各個指標的含義分別為:
avg-cpu說明:
%user:在用戶級別運行所使用的CPU的百分比。
%nice:帶nice值(和進程優先級相關)的用戶模式下運行所使用的CPU的百分比。
%system:在系統級別運行所使用CPU的百分比。
%iowait:CPU等待IO完成的時間百分比。(單個iowait指標值偏高并不能說明磁盤存在IO瓶頸,下面會有詳述。)
%steal:管理程序維護另一個虛擬處理器時,虛擬CPU的無意識等待時間的百分比。
%idle:CPU空閑時間的百分比。(idle值高,表示CPU較空閑。)
device說明:
rrqm/s:每秒進行merge的讀操作數目。即:rmerge/s
wrqm/s:每秒進行merge的寫操作數目。即:wmerge/s
r/s:每秒完成的讀IO設備的次數。即rio/s
w/s:每秒完成的寫IO設備的次數。即wio/s
rsec/s:每秒讀扇區數。即rsect/s(每個扇區大小為512B。)
wsec/s:每秒寫扇區數。即wsect/s
avgrq-sz:平均每次設備IO操作的數據大小(扇區);平均單次IO大小。
avgqu-sz:平均IO隊列長度。
await:從請求磁盤操作到系統完成處理,每次請求的平均消耗時間,包括請求隊列等待時間;平均IO響應時間(毫秒)。
svctm:平均每次設備IO操作的服務時間(毫秒)。
%util:一秒中有百分之多少的時間用于 I/O 操作,即被io消耗的cpu百分比。
正常情況下svctm應該是小于await值的,而svctm的大小和磁盤性能有關,CPU、內存的負荷也會對svctm值造成影響,過多的請求也會間接的導致svctm值的增加。await值的大小一般取決于svctm的值和IO隊列的長度以及IO請求模式,如果scvtm比較接近await,說明IO幾乎沒有等待時間;如果await遠大于svctm,說明IO請求隊列太長,IO響應太慢,則需要進行必要優化。(可以看完下面一節再來回顧這段內容。)
如果%util接近100%,說明產生的IO請求太多,IO系統已經滿負荷,該磁盤可能存在瓶頸。
隊列長度(avgqu-sz)也可作為衡量系統 I/O 負荷的指標,但由于 avgqu-sz 是按照單位時間的平均值,所以不能反映瞬間的 I/O 泛洪,如果avgqu-sz比較大,則說明有大量IO在等待。
二、相關原理
對于await, svctm以及%util等,光從概念上來說,比較晦澀,可以通過下圖的磁盤IO流程來加深理解:
(此圖來源于遺產流....重新畫了一遍)
磁盤IO場景
1. 用戶調用標準C庫進行IO操作,數據流為:應用程序buffer->C庫標準IObuffer->文件系統page cache->通過具體文件系統到磁盤。
2. 用戶調用文件IO,數據流為:應用程序buffer->文件系統page cache->通過具體文件系統到磁盤。
3. 用戶打開文件時使用O_DIRECT,繞過page cache直接讀寫磁盤。
4. 用戶使用類似dd工具,并使用direct參數,繞過系統cache與文件系統直接寫磁盤。
發起IO請求請的步驟簡析(以最長鏈路為例)
寫操作:
1. 用戶調用fwrite把數據寫C庫標準IObuffer后就返回,即寫操作通常是個異步操作。
2. 數據到C庫標準IObuffer后,不會立即刷新到磁盤,會將多次小數據量相鄰寫操作先緩存起來合并,最終調用write函數一次性寫入(或者將大塊數據分解多次write調用)page cache。
3. 數據到page cache后也不會立即刷新到磁盤,內核有pdflush線程在不停的檢測臟頁,判斷是否要寫回到磁盤中,如果是則發起磁盤IO請求。
讀操作:
1. 用戶調用fread到C庫標準IObuffer讀取數據,如果成功則返回,否則繼續。
2. 到page cache讀取數據,如果成功則返回,否則繼續。
3. 發起IO請求,讀取到數據后緩存buffer和C庫標準IObuffer并返回。可以看出,讀操作是同步請求。
IO請求處理
1. 通用塊層根據IO請求構造一個或多個bio結構并提交給調度層。bio結構描述對一個磁盤扇區讀/寫操作。
2. 調度器將bio結構進行排序和合并組織成隊列且確保讀寫操作盡可能理想:將一個或多個進程的讀操作合并到一起讀,將一個或多個進程的寫操作合并到一起寫,盡可能變隨機為順序(因為隨機讀寫比順序讀寫要慢),讀必須優先滿足,而寫也不能等太久。
IO調度算法
linux的IO調度器有時也稱之為磁盤調度器,工作機制是控制塊設備的請求隊列,確定隊列中那些IO的優先級更高以及何時下發IO到塊設備,以此來減少磁盤尋到時間,從而提高系統的吞吐量。目前Linux共有如下幾種IO調度算法:
1. NOOP
NOOP算法的全寫為No Operation。該算法實現了最最簡單的FIFO隊列,所有IO請求大致按照先來后到的順序進行操作。之所以說“大致”,原因是NOOP在FIFO的基礎上還做了相鄰IO請求的合并,并不是完完全全按照先進先出的規則滿足IO請求。
假設有如下的io請求序列:
100,500,101,10,56,1000
NOOP將會按照如下順序滿足:
100(101),500,10,56,1000
2、CFQ
CFQ算法的全寫為Completely Fair Queuing。該算法的特點是按照IO請求的地址進行排序,而不是按照先來后到的順序來進行響應。
假設有如下的io請求序列:
100,500,101,10,56,1000
CFQ將會按照如下順序滿足:
100,101,500,1000,10,56
CFQ是默認的磁盤調度算法,對于通用服務器來說最好的選擇。它視圖均勻地分布對IO帶寬的訪問。CFQ為每個進程單獨創建一個隊列來管理該進程所產生的請求,也就是說每個進程一個隊列,各隊列之間的調度使用時間片來調度,以此來保證每個進程都能被很好的分配到IO帶寬。IO調度器每次執行一個進程的4次請求。在傳統的SAS盤上,磁盤尋道花去了絕大多數的IO響應時間。CFQ的出發點是對IO地址進行排序,以盡量少的磁盤旋轉次數來滿足盡可能多的IO請求。在CFQ算法下,SAS盤的吞吐量大大提高了。但是相比于NOOP的缺點是,先來的IO請求并不一定能被滿足,可能會出現餓死的情況。
3、DEADLINE
DEADLINE在CFQ的基礎上,解決了IO請求餓死的極端情況。除了CFQ本身具有的IO排序隊列之外,DEADLINE額外分別為讀IO和寫IO提供了FIFO隊列。讀FIFO隊列的最大等待時間為500ms,寫FIFO隊列的最大等待時間為5s。FIFO隊列內的IO請求優先級要比CFQ隊列中的高,而讀FIFO隊列的優先級又比寫FIFO隊列的優先級高。優先級可以表示如下:
FIFO(Read) > FIFO(Write) > CFQ
4、ANTICIPATORY
CFQ和DEADLINE考慮的焦點在于滿足零散IO請求上。對于連續的IO請求,比如順序讀,并沒有做優化。為了滿足隨機IO和順序IO混合的場景,Linux還支持ANTICIPATORY調度算法。ANTICIPATORY的在DEADLINE的基礎上,為每個讀IO都設置了6ms的等待時間窗口。如果在這6ms內OS收到了相鄰位置的讀IO請求,就可以立即滿足。 anticipatory 算法通過增加等待時間來獲得更高的性能,假設一個塊設備只有一個物理查找磁頭(例如一個單獨的SATA硬盤),將多個隨機的小寫入流合并成一個大寫入流(相當于給隨機讀寫變順序讀寫),使用這個原理來使用讀取寫入的延時換取最大的讀取寫入吞吐量.適用于大多數環境,特別是讀取寫入較多的環境。
不同的磁盤調度算法(以及相應的IO優化手段)對Kafka這類依賴磁盤運轉的應用的影響很大,建議根據不同的業務需求來測試選擇合適的磁盤調度算法(以后的文章中會有相關的測試介紹)。
查看設備當前的IO調度器:cat /sys/block/{DEVICE-NAME}/queue/scheduler。其中{DEVICE-NAME}指的是磁盤設備的名稱,即文章開頭iostat -x中Device下方的vda,vdb等。
舉例:
[root@hidden ~]# cat /sys/block/vda/queue/scheduler noop anticipatory deadline [cfq]
修改當前的IO調度器: echo {SCHEDULER-NAME} > /sys/block/{DEVICE-NAME}/queue/scheduler。其中{SCHEDULER-NAME}取值為noop、anticipatory、deadline、cfq其中之一。
舉例:
[root@hidden ~]# echo noop > /sys/block/vda/queue/scheduler [root@hidden ~]# cat /sys/block/vda/queue/scheduler [noop] anticipatory deadline cfq
以上設置重啟之后會失效,如果要想重啟后配置仍然生效,需要在內核啟動參數中將elevator={SCHEDULER-NAME}寫入/boot/grub/menu.lst文件中。在修改這個文件之前最好先備份一份,然后將elevator={SCHEDULER-NAME}添加到文件末尾即可。
三、iowait
單獨拎出iowait來說明是因為很多人對這個指標有一定的誤區,包括筆者也經常把iowait和await混淆起來
。顧名思義,就是系統因為io導致的進程wait。再深一點講就是:這時候系統在做IO,導致沒有進程在干活,cpu在執行idle進程空轉,所以說iowait的產生要滿足兩個條件,一是進程在等IO,二是等IO時沒有進程可運行。
常用的top命令中也有iowait的指標展示(%wa就是%iowait),示例如下:
對 iowait 常見的誤解有兩個:1. 誤以為 iowait 表示CPU不能工作的時間;2. 誤以為 iowait 表示I/O有瓶頸問題。
iowait 的首要條件就是CPU空閑,既然空閑當然就可以接受運行任務,只是因為沒有進程可以運行,CPU才進入空閑狀態的。那為什么沒有進程可以運行呢?因為進程都處于休眠狀態、在等待某個特定事件:比如等待定時器、或者來自網絡的數據、或者鍵盤輸入、或者等待I/O操作完成,等等。iowait的升高并不能證明等待IO進程的數量增多了,也不能證明等待IO的總時間增加了。例如,在CPU繁忙期間發生的I/O,無論IO是多還是少,iowait都不會變;當CPU繁忙程度下降時,有一部分IO落入CPU空閑時間段內,導致iowait升高。再比如,IO的并發度低,iowait就高;IO的并發度高,iowait可能就比較低。所以iowait 所含的信息量非常少,它是一個非常模糊的指標,如果看到 iowait 升高,還需檢查I/O量有沒有明顯增加,相應的一些指標有沒有明顯增大,應用有沒有感覺變慢,如果都沒有,就沒什么好擔心的。
Plus: 可以使用iotop命令來查找引起高iowait對應的進程。查看CPU使用率及負載的一些命令有:top、vmstat、mpstat、uptime等。