磁盤 IO 性能 調優 多線程

1. 完全隨機寫還是跳躍,5倍的性能差距!

全隨機寫無疑是最慢的寫入方式,在logic dump測試中很驚訝的發現,將200M的內存數據隨 機的寫入到100G的磁盤數據裏面,竟然要2個小時之多。原因就是雖然只有200M的數據,但實際上卻是200萬次隨機寫,根據測試,在2850機器上, 這樣完全的隨機寫,r/s 大約在150~350之間,在180機器上,r/s難以達到250,這樣計算,難怪需要2~3個小時之久。

如何改進這種單線程隨機寫慢的問題呢。一種方法就是儘量將完全隨機寫變成有序的跳躍隨機寫。實現方式,可以是簡單的在內存中緩存一段時間,然後排序,使得在 寫盤的時候,不是完全隨機的,而是使得磁盤磁頭的移動只向一個方向。根據測試,再一次讓我震驚,簡單的先在內存中排序,竟然直接使得寫盤時間縮短到1645秒,磁盤的r/s也因此提升到1000以上。寫盤的速度,一下子提高了5倍。

一個需要注意的地方,這種跳躍寫對性能的提升,來至與磁頭的單方向移動,它非常容易受其他因素的影響。測試中,上面提到的測試是隻寫block文件, 但如果在每個tid的處理中再增加一個寫index的小文件。雖然如果只寫index小文件,所用時間幾乎可以忽略,但如果夾雜在寫block文件中間的 話,對整體的寫性能可能影響巨大,因爲他可能使得磁盤的磁頭需要這兩個地方來回跑。根據測試,如果只寫index文件,只需要300s就可以寫完所有 200萬個tid,單如果將寫索引和寫block放在一起,總時間就遠大於分別寫這兩部分的時間的和。針對這種情況,一種解決方案就是就不要將小數據量的數據實時的刷盤,使用應用層的cache來緩存小數據量的index,這樣就可以消除對寫block文件的影響。

從原理上解釋上面的表象,一般來說,硬盤讀取數據的過程是這樣的,首先是將磁頭移動到磁盤上數據所在的區域,然後才能進行讀取工作。磁頭移動的過程又可以 分解爲兩個步驟,其一是移動磁頭到指定的磁道,也就是尋道,這是一個在磁盤盤片徑向上移動的步驟,花費的時間被稱爲“尋道時間”;其二就是旋轉盤片到相應 扇區,花費的時間被稱爲“潛伏時間”(也被稱爲延遲)。那麼也就是說在硬盤上讀取數據之前,做準備工作上需要花的時間主要就是“尋道時間”和“潛伏時間” 的總和。真正的數據讀取時間,是由讀取數據大小和磁盤密度、磁盤轉速決定的固定值,在應用層沒有辦法改變,但應用層缺可以通過改變對磁盤的訪問模式來減少“尋道時間”和“潛伏時間”, 我們上面提到的在應用層使用cache然後排序的方式,無疑就是縮短了磁盤的尋址時間。由於磁頭是物理設備,也很容易理解,爲什麼中間插入對其他小文件的讀寫會導致速度變慢很多。

建議:儘量避免完全的隨機寫,在 不能使用多線處理的時候,儘量使用應用層cache,確保寫盤時儘量有順序性。對於小數據量的其他文件,可以一直保存在應用層cache裏面,避免對其他大數據量的數據寫入產生影響。

2. 多線程隨機讀、處理速度、響應時間

多線程隨機讀的處理速度可以達到單線程隨機讀的10倍以上,但同上也帶來了響應時間的增大。測試結論如下:(每個線程儘量讀)

結論標明增加線程數,可以有效的提升程序整體的io處理速度。但同時,也使得每個io請求的響應時間上升很多。

從底層的實現上解釋這個現象:應用層的io請求在內核態會加入到io請求隊列裏面。內核在處理io請求的時候,並不是簡單的先到先處理,而是根據磁盤的特 性,使用某種電梯算法,在處理完一個io請求後,會優先處理最臨近的io請求。這樣可以有效的減少磁盤的尋道時間,從而提升了系統整體的io處理速度。但對於每一個io請求來看,由於可能需要在隊列裏面等待,所以響應時間會有所提升。

響應時間上升,應該主要是由於我們測試的時候採用每個線程都儘量讀的方式。在實際的應用中,我們的程序都沒有達到這種壓力。所以,在io成爲瓶頸的程序裏面,應該儘量使用多線程並行處理不同的請求。對於線程數的選擇,還需要通過性能測試來衡量。

3. 是否使用direct io

首先看測試結論:

 

可見在小數據量下非dio方式更快,但隨着數據量增大,dio方式更快,分界線在50G左右。(注,測試基於: 線程數:50,每次讀操作讀出:4K, 機器:del 180, 內存:機器總內存8G,使用其他程序佔用3G,剩餘5G左右, 其他情況可能有不同的分界線。)

4. 系統緩存

4.1. 系統緩存相關的幾個內核參數:
1. /proc/sys/vm/dirty_background_ratio
該文件表示髒數據到達系統整體內存的百分比,此時觸發pdflush進程把髒數據寫回磁盤。
缺省設置:10

2. /proc/sys/vm/dirty_expire_centisecs
該文件表示如果髒數據在內存中駐留時間超過該值,pdflush進程在下一次將把這些數據寫回磁盤。
缺省設置:3000(1/100秒)

3. /proc/sys/vm/dirty_ratio
該文件表示如果進程產生的髒數據到達系統整體內存的百分比,此時進程自行把髒數據寫回磁盤。
缺省設置:40

4. /proc/sys/vm/dirty_writeback_centisecs
該文件表示pdflush進程週期性間隔多久把髒數據寫回磁盤。
缺省設置:500(1/100秒)

4.2. 系統一般在下面三種情況下回寫dirty頁:

1. 定時方式: 定時回寫是基於這樣的原則:/proc/sys/vm/dirty_writeback_centisecs的值表示多長時間會啓動回寫線程,由這個定時 器啓動的回寫線程只回寫在內存中爲dirty時間超過(/proc/sys/vm/didirty_expire_centisecs / 100)秒的頁(這個值默認是3000,也就是30秒),一般情況下dirty_writeback_centisecs的值是500,也就是5秒,所以 默認情況下系統會5秒鐘啓動一次回寫線程,把dirty時間超過30秒的頁回寫,要注意的是,這種方式啓動的回寫線程只回寫超時的dirty頁,不會回寫 沒超時的dirty頁,可以通過修改/proc中的這兩個值,細節查看內核函數wb_kupdate。

2. 內存不足的時候: 這時並不將所有的dirty頁寫到磁盤,而是每次寫大概1024個頁面,直到空閒頁面滿足需求爲止

3. 寫操作時發現髒頁超過一定比例: 當髒頁佔系統內存的比例超過/proc/sys/vm/dirty_background_ratio 的時候,write系統調用會喚醒pdflush回寫dirty page,直到髒頁比例低於/proc/sys/vm/dirty_background_ratio,但write系統調用不會被阻塞,立即返回.當髒 頁佔系統內存的比例超/proc/sys/vm/dirty_ratio的時候, write系統調用會被被阻塞,主動回寫dirty page,直到髒頁比例低於/proc/sys/vm/dirty_ratio

4.3. pb項目中的感觸:
1,如果寫入量巨大,不能期待系統緩存的自動回刷機制,最好採用應用層調用fsync或者sync。如果寫入量大,甚至超過了系統緩存自動刷回的速 度,就有可能導致系統的髒頁率超過/proc/sys/vm/dirty_ratio, 這個時候,系統就會阻塞後續的寫操作,這個阻塞有可能有5分鐘之久,是我們應用無法承受的。因此,一種建議的方式是在應用層,在合適的時機調用 fsync。

2,對於關鍵性能,最好不要依賴於系統cache的作用,如果對性能的要求比較高,最好在應用層自己實現cache,因爲系統cache受外界影響太大,說不定什麼時候,系統cache就被沖走了。

3,在logic設計中,發現一種需求使用系統cache實現非常合適,對於logic中的高樓貼,在應用層cache實現非常複雜,而其數量又非常 少,這部分請求,可以依賴於系統cache發揮作用,但需要和應用層cache相配合,應用層cache可以cache住絕大部分的非高樓貼的請求,做到 這一點後,整個程序對系統的io就主要在高樓貼這部分了。這種情況下,系統cache可以做到很好的效果。

5. 磁盤預讀

關於預讀,從網上摘錄如下兩段:
預讀算法概要
1. 順序性檢測
爲了保證預讀命中率,Linux只對順序讀(sequential read)進行預讀。內核通過驗證如下兩個條件來判定一個read()是否順序讀:

  • 這是文件被打開後的第一次讀,並且讀的是文件首部;
  • 當前的讀請求與前一(記錄的)讀請求在文件內的位置是連續的。

如果不滿足上述順序性條件,就判定爲隨機讀。任何一個隨機讀都將終止當前的順序序列,從而終止預讀行爲(而不是縮減預讀大小)。注意這裏的空間順序性說的 是文件內的偏移量,而不是指物理磁盤扇區的連續性。在這裏Linux作了一種簡化,它行之有效的基本前提是文件在磁盤上是基本連續存儲的,沒有嚴重的碎片 化。

2. 流水線預讀
當程序在處理一批數據時,我們希望內核能在後臺把下一批數據事先準備好,以便CPU和硬盤能流水線作業。Linux用兩個預讀窗口來跟蹤當前順序流的預讀 狀態:current窗口和ahead窗口。其中的ahead窗口便是爲流水線準備的:當應用程序工作在current窗口時,內核可能正在ahead窗 口進行異步預讀;一旦程序進入當前的ahead窗口,內核就會立即往前推進兩個窗口,並在新的ahead窗口中啓動預讀I/O。

3. 預讀的大小
當確定了要進行順序預讀(sequential readahead)時,就需要決定合適的預讀大小。預讀粒度太小的話,達不到應有的性能提升效果;預讀太多,又有可能載入太多程序不需要的頁面,造成資源浪費。爲此,Linux採用了一個快速的窗口擴張過程:
首次預讀: readahead_size = read_size * 2; // or *4
預讀窗口的初始值是讀大小的二到四倍。這意味着在您的程序中使用較大的讀粒度(比如32KB)可以稍稍提升I/O效率。

後續預讀: readahead_size *= 2;
後續的預讀窗口將逐次倍增,直到達到系統設定的最大預讀大小,其缺省值是128KB。這個缺省值已經沿用至少五年了,在當前更快的硬盤和大容量內存面前,顯得太過保守。
# blockdev –setra 2048 /dev/sda
當然預讀大小不是越大越好,在很多情況下,也需要同時考慮I/O延遲問題。

6. 其他細節:

6.1. pread 和pwrite
在多線程io操作中,對io的操作儘量使用pread和pwrite,否則,如果使用seek+write/read的方式的話,就需要在操作時加鎖。這種加鎖會直接造成多線程對同一個文件的操作在應用層就串行了。從而,多線程帶來的好處就被消除了。

使用pread方式,多線程也比單線程要快很多,可見pread系統調用並沒有因爲同一個文件描述符而相互阻塞。pread和pwrite系統調用在底層 實現中是如何做到相同的文件描述符而彼此之間不影響的?多線程比單線程的IOPS增高的主要因素在於調度算法。多線程做pread時相互未嚴重競爭是次要 因素。

內核在執行pread的系統調用時並沒有使用inode的信號量,避免了一個線程讀文件時阻塞了其他線程;但是pwrite的系統調用會使用inode的 信號量,多個線程會在inode信號量處產生競爭。pwrite僅將數據寫入cache就返回,時間非常短,所以競爭不會很強烈。

6.2. 文件描述符需要多套嗎?
在使用pread/pwrite的前提下,如果各個讀寫線程使用各自的一套文件描述符,是否還能進一步提升io性能?
每個文件描述符對應內核中一個叫file的對象,而每個文件對應一個叫inode的對象。假設某個進程兩次打開同一個文件,得到了兩個文件描述符,那麼在 內核中對應的是兩個file對象,但只有一個inode對象。文件的讀寫操作最終由inode對象完成。所以,如果讀寫線程打開同一個文件的話,即使採用 各自獨佔的文件描述符,但最終都會作用到同一個inode對象上。因此不會提升IO性能

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章