效能分析之又見jbd2引起IO高
之前遇到過jbd2引起IO高的問題,直接關掉了日誌的功能解決的。寫了一個文章,但寫的不夠細。最近又見類似問題,這裡重新整理下對jbd2的內容。
什麼原因會導致jbd2引起IO高?
-
磁碟滿.
-
系統bug;所知bug號:Bug 39072 - jbd2 writes on disk every few seconds。
-
即使沒有以上問題。在ext4上有一個新加入的引數barrier,是用來保證檔案系統的完整性的。 [Barrier解釋](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/block/barrier.txt?id=09d60c701b64b509f328cac72970eb894f485b9e)。
這個值預設是1,即是開啟狀態。在這個狀態下,開啟jbd2也是會導致效能下降的,這個玩意的設計邏輯就是為了損失掉效能保證檔案完整性。
這是個選擇題,要麼不用它,要麼效能差。
但是這個功能不能和裝置對映器同時使用,也即是,如果你使用了邏輯卷、軟RAID、多路徑磁碟,則這個值不生效。
jbd2是個什麼?
-
The Journaling Block Device ( JBD ) provides a filesystem - independent interface for filesystem journaling . ext3 , ext4 and OCFS2 are known to use JBD . OCFS2 starting from Linux 2.6 . 28 [ 1 ] and ext4 use a fork of JBD called JBD2 .[ 2 ]
檔案系統的日誌功能,jbd2是ext4檔案系統版本。
檢查是否存在jbd2程序
檔案系統的日誌功能,jbd2是ext4檔案系統版本。
檢查是否存在jbd2程序
檢查檔案系統的功能
存在has_journal。
問題現象
在使用iotop看的時候,會有如下資訊出現。
解決方案一
關閉日誌功能。
如果使用tune2fs時候,提示disk正在mount,如果是非系統盤下,你可以使用
之後在使用上面的命令進行移除has_journal。
解決方案二
如果是bug的話,可以用這種方式解決。如果是不是bug,這種方式也解決不了,所以要先判斷下引起問題的原因再選擇解決方案。
升級系統核心。
解決方案三
禁用Barrier的同時修改commit的值。這個方式可以解決barrier引起的效能下降,但是解決不了系統bug的問題。
修改commit值,降低檔案系統提交次數或者禁用barrier特性; 建議檔案系統引數為:
然後重新掛載。
其中barrier=0是禁用barrier特性,commit=60是減少提交次數。 減少提交次數只能緩解。
解決方案四
如果不是bug,並且不想禁用barrier時,用此方式緩解。
想盡辦法降低IO,緩解IO壓力。這種方式也會導致其他系統資源用不上去。 比如說在mysql中把sync binlog加大,同時將innodb flush log at trx commit增加。 比如說在應用中減少IO的讀寫。
bug的根源
在之前的版本中出現問題有一個原因是ext4檔案系統出現bug。 這個bug出現的比較早了,我看kernel tracker裡最早的資訊是2011年,如果如果是用的老版本,我建議先做升級。如果沒有升級條件,只能用上面的關閉日誌功能的解決方案。
bug原因是: 在這段程式碼中:

中的tid_geq的函式是這樣實現的。
假設j commit request值為2157483647,而target的值為0,看上去 if (!tid geq(journal->j commit_request, target))這個判斷是不會走的。 但是unsigned int的x減去0之後,轉為difference時,difference的定義是int型,此時的結果是多少呢?是-2137483649。 為什麼呢?因為unsigned int型別的最大值是2147483647。
而2157483647 - 0的這個結果顯然溢位了,變成了負數。比如,你可以嘗試這樣列印。
結果就變成了:-1。 有興趣的,可以自己寫個簡單的原始碼試一下。
執行之後是什麼呢?
可見在這種情況下,因為溢位的變數導致if (!tid geq(journal->j commit_request, target))走到了。
這個unsigned int的變數是jbd2給每個transaction的tid,tid是一直增加的,因為這個型別容易溢位,所以用tid geq來判斷下,意思是2157483647這個tid已經提交了,所以把1000號的transaction commit掉,於是執行了wake up(&journal->j wait commit);。但是執行之後才發現,原來並沒有執行中的事務,於是系統就瘋了。 在trace jbd2的可以看到target有0的情況。實際上,大部分的target都不會是0,這個0是因為ialloc.c中的i datasync tid沒有正確賦值,所以使用了預設的0。 i datasync tid是在建立inode或者ext4 iget()時更新的,如果應用在開啟某些檔案後就不再關閉,只是一直更新,這時extent樹是不變的(ext4使用extent取代了傳統的block對映方式),但是j commit_request隨著jbd2日誌的提交而不斷增加,所以最後這個差值會在業務執行到一定時間之後出現負值。 如果是這個bug引起的話,可以看到的現象是jbd2這個程序長時間佔著99%的IO。
影響版本
有此問題的os版本,只根據我使用過的版本列的:

核心版本: