咱們在cpu篇就提到,iowait高通常表明硬盤到瓶頸了。wait的意思,就是等,就像等正在化妝的女友,老是帶着一絲焦躁。本篇是《荒島餘生》系列第四篇,I/O篇,計算機中最慢的那一環。其他參見:java
Linux之《荒島餘生》(二)CPU篇python
Linux之《荒島餘生》(三)內存篇mysql
I/O
不只僅是硬盤,還包括外圍的全部設備,好比鍵盤鼠標,好比1.44M
的3.5
英寸軟盤(還有人記得麼)。但服務器環境,泛指硬盤。react
硬盤有多慢呢?咱們不去探究不一樣設備的實現細節,直接看它的寫入速度(數據有出入,僅做參考):linux
能夠看到普通磁盤的隨機寫和順序寫相差是很是大的。而隨機寫
徹底和cpu內存不在一個數量級。緩衝區依然是解決速度差別的惟一工具,因此在極端狀況好比斷電等,就產生了太多的不肯定性。這些緩衝區,都容易丟。ios
咱們舉例看一下爲了消除這些性能差別,軟件方面都作了哪些權衡。sql
數據庫設計,採用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
問題的命令其實並很少,大致有下面幾個。
#查看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狀況。
bi、bo等在你瞭解磁盤的類型後纔有判斷價值。咱們有更專業的判斷工具,因此這些信息一瞥便可。在本例中,wa
已經達到30%
,證實cpu耗費在上面的時間太多。
如何判斷還須要結合iostat
的幫助。有時候你是迫不得已的,好比這臺MySQL的宿主機。你可能會更換更牛X的磁盤,或者整治耗I/O的慢SQL,再或者去改參數。
iostat
命令就夠了!咱們對一些重要結果進行說明:
%util 最重要的判斷參數。通常地,若是該參數是100%表示設備已經接近滿負荷運行了
Device 表示發生在哪塊硬盤。若是你有多快,則會顯示多行
avgqu-sz 還記得準備篇裏提到的麼?這個值是請求隊列的飽和度,也就是平均請求隊列的長度。毫無疑問,隊列長度越短越好
await 響應時間應該低於5ms,若是大於10ms就比較大了。這個時間包括了隊列時間和服務時間
svctm 表示平均每次設備I/O
操做的服務時間。若是svctm
的值與await
很接近,表示幾乎沒有I/O
等待,磁盤性能很好,若是await
的值遠高於svctm
的值,則表示I/O
隊列等待太長,系統上運行的應用程序將變慢
整體來講,%util
表明了硬盤的繁忙程度,是你進行擴容增長配置的指標。而await
、avgqu-sz
、svctm
等是硬盤的性能指標,若是%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
複製代碼
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使用佔比。
那麼怎麼看應用所關聯的全部文件信息呢?可使用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
,在此不作詳解。
幾個特殊進程說明*
這依然是因爲swap虛擬內存引發的,證實虛擬內存正在大量使用
全稱是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
複製代碼
這很是安全。
kafka比較快的一個緣由就是使用了zero copy。所謂的Zero copy,就是在操做數據時, 不須要將數據buffer從一個內存區域拷貝到另外一個內存區域。由於少了一次內存的拷貝, CPU的效率就獲得提高。
咱們來看一下它們之間的區別:
要想將一個文件的內容經過socket發送出去,傳統的方式須要通過如下步驟: =>將文件內容拷貝到內核空間 =>將內核空間的內容拷貝到用戶空間內存,好比java應用 =>用戶空間將內容寫入到內核空間的緩存中 =>socket讀取內核緩存中的內容,發送出去 如上圖,zero copy在內核的支持下,少了一個步驟,那就是內核緩存向用戶空間的拷貝。即節省了內存,也節省了cpu的調度時間,效率很高。值得注意的是,java中的zero copy,指的實際上是DirectBuffer;而Netty的zero copy是在用戶空間中進行的優化。二者並非一個概念。
面向接口編程?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
問題的定位方法,就不得不提一下Linux下的5
種I/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模式。
除了可以幫助咱們評價I/O瓶頸,一個很是重要的點就是:業務研發要合理輸出日誌,日誌文件不只僅是影響磁盤那麼簡單,它還會耗佔大量的CPU。
對於咱們日常的優化思路,也有章可循。像mysql、es、postgresql等,在寫真正的數據庫文件以前,會有不少層緩衝。若是你對數據可靠性要求並非那麼嚴重,調整這些緩衝參數的閾值和執行間隔,一般會獲得較大的性能提高。
固然,瞭解I/O還能幫助咱們更好的理解一些軟件的設計理念。好比leveldb是如何經過LSM來組織數據;ES爲何會存在那麼多的段合併;甚至Redis爲什麼存在。
固然,你可能再也沒法忍受單機硬盤的這些特性,轉而尋求像ceph這樣的解決方案。但不管如何,咱們都該向全部的數據庫研發工做者致敬,在很長一段時間裏,咱們依然須要和緩慢的I/O共行。