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

咱們在cpu篇就提到,iowait高通常表明硬盤到瓶頸了。wait的意思,就是等,就像等正在化妝的女友,老是帶着一絲焦躁。本篇是《荒島餘生》系列第四篇,I/O篇,計算機中最慢的那一環。其他參見:java

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

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

Linux之《荒島餘生》(三)內存篇mysql

一點背景

速度差別

I/O不只僅是硬盤,還包括外圍的全部設備,好比鍵盤鼠標,好比1.44M3.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問題的通常思路

判斷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 表明了硬盤的繁忙程度,是你進行擴容增長配置的指標。而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使用佔比。

那麼怎麼看應用所關聯的全部文件信息呢?可使用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的效率就獲得提高。

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

要想將一個文件的內容經過socket發送出去,傳統的方式須要通過如下步驟: =>將文件內容拷貝到內核空間 =>將內核空間的內容拷貝到用戶空間內存,好比java應用 =>用戶空間將內容寫入到內核空間的緩存中 =>socket讀取內核緩存中的內容,發送出去

如上圖,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模式。

咱們能獲得什麼

除了可以幫助咱們評價I/O瓶頸,一個很是重要的點就是:業務研發要合理輸出日誌,日誌文件不只僅是影響磁盤那麼簡單,它還會耗佔大量的CPU。

對於咱們日常的優化思路,也有章可循。像mysql、es、postgresql等,在寫真正的數據庫文件以前,會有不少層緩衝。若是你對數據可靠性要求並非那麼嚴重,調整這些緩衝參數的閾值和執行間隔,一般會獲得較大的性能提高。

固然,瞭解I/O還能幫助咱們更好的理解一些軟件的設計理念。好比leveldb是如何經過LSM來組織數據;ES爲何會存在那麼多的段合併;甚至Redis爲什麼存在。

固然,你可能再也沒法忍受單機硬盤的這些特性,轉而尋求像ceph這樣的解決方案。但不管如何,咱們都該向全部的數據庫研發工做者致敬,在很長一段時間裏,咱們依然須要和緩慢的I/O共行。

相關文章
相關標籤/搜索