Linux之《荒島餘生》(四)I/O篇

小姐姐味道發表於2018-12-22

我們在cpu篇就提到,iowait高一般代表硬碟到瓶頸了。wait的意思,就是等,就像等正在化妝的女朋友,總是帶著一絲焦躁。本篇是《荒島餘生》系列第四篇,I/O篇,計算機中最慢的那一環。其餘參見:

Linux之《荒島餘生》(一)準備篇

Linux之《荒島餘生》(二)CPU篇

Linux之《荒島餘生》(三)記憶體篇

一點背景

速度差異

I/O不僅僅是硬碟,還包括外圍的所有裝置,比如鍵盤滑鼠,比如1.44M3.5英寸軟盤(還有人記得麼)。但伺服器環境,泛指硬碟。

硬碟有多慢呢?我們不去探究不同裝置的實現細節,直接看它的寫入速度(資料有出入,僅作參考):

Linux之《荒島餘生》(四)I/O篇

可以看到普通磁碟的隨機寫和順序寫相差是非常大的。而隨機寫完全和cpu記憶體不在一個數量級。緩衝區依然是解決速度差異的唯一工具,所以在極端情況比如斷電等,就產生了太多的不確定性。這些緩衝區,都容易丟。

我們舉例看一下為了消除這些效能差異,軟體方面都做了哪些權衡。

  • 資料庫設計,採用BTree結構組織資料,通過減少對磁碟的訪問和隨機讀取,來提高效能

  • Postgres通過順序寫WAL日誌、ES通過寫translog等,通過預寫,避免斷電後資料丟失問題

  • Kafka通過順序寫來增加效能,但在topic非常多的情況下效能弱化為隨機寫

  • Kafka通過零拷貝技術,利用DMA繞過記憶體直接傳送資料

  • Redis使用記憶體模擬儲存,它流行的主要原因就是和硬碟打交道的傳統DB速度太慢

  • 回憶一下記憶體篇的buffer區,是用來緩衝寫入硬碟的資料的。linux的sync命令可以將buffer的資料刷到硬碟上,突然斷電的話,就不好說了

做一個記憶體盤

如果你的記憶體夠大,那麼可以做一個記憶體盤。跑遊戲,做檔案交換什麼的不要太爽。

mkdir /memdisk
mount  -t tmpfs -o size=1024m  tmpfs /memdisk/
複製程式碼

以上命令劃出1GB記憶體,掛載到/memdisk目錄,然後就可以像使用普通資料夾一樣使用它了。只是,速度不可同日而語。

使用dd命令測試寫入速度

[root@xjj memdisk]# time dd if=/dev/zero of=test.file bs=4k count=200000
200000+0 records in
200000+0 records out
819200000 bytes (819 MB) copied, 0.533173 s, 1.5 GB/s

real	0m0.534s
user	0m0.020s
sys	0m0.510s
複製程式碼

你見過這麼快的硬碟麼?

排查I/O問題的一般思路

判斷I/O問題的命令其實並不多,大體有下面幾個。

#檢視wa
top 
#檢視wa和io(bi、bo)
vmstat 1
#檢視效能相關i/o詳情
sar -b 1 2
# 檢視問題相關i/o詳情
iostat -x 1
# 檢視使用i/o最多的程式
iotop
複製程式碼

驚鴻一瞥

首先是我們的老面孔。top、vmstat、sar命令,可以初步判斷io情況。

Linux之《荒島餘生》(四)I/O篇
bi、bo等在你瞭解磁碟的型別後才有判斷價值。我們有更專業的判斷工具,所以這些資訊一瞥即可。

在本例中,wa已經達到30%,證明cpu耗費在上面的時間太多。

定位問題

如何判斷還需要結合iostat的幫助。有時候你是無可奈何的,比如這臺MySQL的宿主機。你可能會更換更牛X的磁碟,或者整治耗I/O的慢SQL,再或者去改引數。

Linux之《荒島餘生》(四)I/O篇
你瞧瞧,其實一個iostat命令就夠了!我們對一些重要結果進行說明:

  • %util 最重要的判斷引數。一般地,如果該引數是100%表示裝置已經接近滿負荷執行了

  • Device 表示發生在哪塊硬碟。如果你有多快,則會顯示多行

  • avgqu-sz 還記得準備篇裡提到的麼?這個值是請求佇列的飽和度,也就是平均請求佇列的長度。毫無疑問,佇列長度越短越好

  • await 響應時間應該低於5ms,如果大於10ms就比較大了。這個時間包括了佇列時間和服務時間

  • svctm 表示平均每次裝置I/O操作的服務時間。如果svctm的值與await很接近,表示幾乎沒有I/O等待,磁碟效能很好,如果await的值遠高於svctm的值,則表示I/O佇列等待太長,系統上執行的應用程式將變慢

總體來說,%util 代表了硬碟的繁忙程度,是你進行擴容增加配置的指標。而awaitavgqu-szsvctm等是硬碟的效能指標,如果%util正常的情況下反應異常則代表你的磁碟可能存在問題。

iostat列印出的第1個報告,數值是基於最後一次系統啟動的時間統計的;基於這個原因,在大部份情況下,iostat列印出的第1個報告應該被忽略。

另外一種方式就是通過ps命令或者top命令得到狀態為D的程式。比如下面命令,迴圈10次進行狀態抓取。

for x in `seq 1 1 10`; do ps -eo state,pid,cmd | grep "^D"; echo "----"$x; sleep 5; done
複製程式碼

找到I/O大戶

iostat檢視的是硬碟整體的狀況,但我們想知道到底是哪個應用引起的。top系列有一個iotop,能夠像top一樣,看到佔用I/O最多的應用。iotop的本質是一個python指令碼,從proc目錄中獲取thread的IO資訊,進行彙總。比如

[root@xjj ~]# cat  /proc/5178/io
rchar: 628
wchar: 461
syscr: 2
syscw: 8
read_bytes: 0
write_bytes: 0
cancelled_write_bytes: 0
複製程式碼

如圖,顯示了當前系統硬碟的讀寫速度和應用的I/O使用佔比。

Linux之《荒島餘生》(四)I/O篇

那麼怎麼看應用所關聯的所有檔案資訊呢?可以使用lsof命令,列出了所有的引用控制程式碼。

[root@xjj ~]# lsof -p 4050
COMMAND  PID  USER   FD   TYPE             DEVICE     SIZE/OFF      NODE NAME
mysqld  4050 mysql  314u  IPv6          115230644          0t0       TCP iZ2zeeaoqoxksuhnqbgfjjZ:mysql->10.30.134.8:54943 (ESTABLISHED)
mysqld  4050 mysql  320u   REG             253,17         2048  44829072 /data/mysql/mysql/user.MYI
mysqld  4050 mysql  321u   REG             253,17         3108  44829073 /data/mysql/mysql/user.MYD
...
複製程式碼

更深層的資訊,可以通過類似Percona-Toolkit這種工具去深入排查,比如pt-ioprofile,在此不做詳解。

幾個特殊程式說明*

kswapd0

這依然是由於swap虛擬記憶體引起的,證明虛擬記憶體正在大量使用

jbd2

全稱是journaling block driver。這個程式實現的是檔案系統的日誌功能,磁碟使用日誌功能來保證資料的完整性。

可以通過以下方法將其關掉,但一定要權衡

dumpe2fs /dev/sda1
tune2fs -o journal_data_writeback /dev/sda1
tune2fs -O "^has_journal" /dev/sda1
e2fsck -f /dev/sda1
複製程式碼

同時在fstab下重新設定一下,在defaults之後增加

defaults,data=writeback,noatime,nodiratime
複製程式碼

你可能會有一個數量級的效能提升。

其他

硬碟快滿了

使用df命令可以看到磁碟的使用情況。一般,使用達到90%就需要重點關注,然後人工介入刪除檔案了。

[root@xjj ~]# df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/vda1        40G  8.3G   29G  23% /
/dev/vdb1      1008G   22G  935G   3% /data
tmpfs           1.0G  782M  243M  77% /memdisk
複製程式碼

使用du命令可以檢視某個檔案的大小。

[root@xjj ~]# du -h test.file
782M	test.file
複製程式碼

如果想把一個檔案置空,千萬不要直接rm。其他應用可能保持著它的引用,經常發上檔案刪除但空間不釋放的問題。比如tomcat的calatina.out檔案,如果你想清空裡面的內容,不要rm,可以執行下面的命令進行檔案內容清空

cat /dev/null > calatina.out 
複製程式碼

這非常安全。

zero copy

kafka比較快的一個原因就是使用了zero copy。所謂的Zero copy,就是在運算元據時, 不需要將資料buffer從一個記憶體區域拷貝到另一個記憶體區域。因為少了一次記憶體的拷貝, CPU的效率就得到提升。

我們來看一下它們之間的區別:

Linux之《荒島餘生》(四)I/O篇
要想將一個檔案的內容通過socket傳送出去,傳統的方式需要經過以下步驟: =>將檔案內容拷貝到核心空間 =>將核心空間的內容拷貝到使用者空間記憶體,比如java應用 =>使用者空間將內容寫入到核心空間的快取中 =>socket讀取核心快取中的內容,傳送出去

Linux之《荒島餘生》(四)I/O篇
如上圖,zero copy在核心的支援下,少了一個步驟,那就是核心快取向使用者空間的拷貝。即節省了記憶體,也節省了cpu的排程時間,效率很高。

值得注意的是,java中的zero copy,指的其實是DirectBuffer;而Netty的zero copy是在使用者空間中進行的優化。兩者並不是一個概念。

Linux通用I/O模型

面向介面程式設計?linux從誕生開始就有了。在linux下,一切都是檔案,比如裝置、指令碼、可執行檔案、目錄等。操作它們,都有公用的介面。所以,編寫一個裝置驅動,就是實現這些介面而已。

  • fd = open(pathname, flags, mode)

  • rlen = read(fd, buf, count)

  • wlen = write(fd, buf, count)

  • status = close(fd)

使用stat命令可以看到檔案的一些狀態。

[root@xjj ~]# stat test.file
  File: ‘test.file’
  Size: 819200000 	Blocks: 1600000    IO Block: 4096   regular file
Device: 26h/38d	Inode: 3805851     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-11-29 12:56:34.801412100 +0800
Modify: 2018-11-29 12:56:35.334415131 +0800
Change: 2018-11-29 12:56:35.334415131 +0800
複製程式碼

而使用file命令,能得到檔案的型別資訊

[root@xjj ~]# file /bin/bash
/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=ab347e897f002d8e3836479e2430d75305fe6a94, stripped
複製程式碼

I/O模型

談完了I/O問題的定位方法,就不得不提一下Linux下的5I/O模型。等等,這其實是一個網路問題。

  • 同步阻塞IO(Blocking IO) 傳統的IO模型

  • 同步非阻塞IO(Non-blocking IO) 非阻塞IO要求socket被設定為NONBLOCK

  • IO多路複用(IO Multiplexing) 即經典的Reactor設計模式

  • 非同步IO(Asynchronous IO) 即經典的Proactor設計模式

java中nio使用的就是多路複用功能,也就是使用的Linux的epoll庫。一般手擼nio的比較少了,大都是直接使用netty進行開發。它們用到的,就是經典的reactor模式。

Linux之《荒島餘生》(四)I/O篇

我們能得到什麼

除了能夠幫助我們評價I/O瓶頸,一個非常重要的點就是:業務研發要合理輸出日誌,日誌檔案不僅僅是影響磁碟那麼簡單,它還會耗佔大量的CPU。

對於我們平常的優化思路,也有章可循。像mysql、es、postgresql等,在寫真正的資料庫檔案之前,會有很多層緩衝。如果你對資料可靠性要求並不是那麼嚴重,調整這些緩衝引數的閾值和執行間隔,通常會得到較大的效能提升。

當然,瞭解I/O還能幫助我們更好的理解一些軟體的設計理念。比如leveldb是如何通過LSM來組織資料;ES為什麼會存在那麼多的段合併;甚至Redis為何存在。

當然,你可能再也無法忍受單機硬碟的這些特性,轉而尋求像ceph這樣的解決方案。但無論如何,我們都該向所有的資料庫研發工作者致敬,在很長一段時間裡,我們依然需要和緩慢的I/O共行。

Linux之《荒島餘生》(四)I/O篇

相關文章