Redis與磁盤IO阻塞

內存中的數據寫到磁盤,會經過vfs~fs~邏輯卷/軟raid~pagecache~塊操作調度~磁盤等一系列過程。
磁盤IO阻塞會涉及到操作系統方面的很多細節。了解這些細節,對編程開發以及運維工作都是有利的。
本文中將結合Redis,講述磁盤IO阻塞。

一、IO阻塞場景

Redis容易出現磁盤IO阻塞場景:
a)追加aof日志
b)rewrite aof
c)頻繁dump rdb

分析這幾個場景的代碼,發現在Redis中調用的posix接口中,WritefSyncCloseRenameUnlink都有可能造成阻塞。
下面我們結合代碼來分析它們是如何造成阻塞的。

二、場景分析

1.追加aof日志

#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
void flushAppendOnlyFile(int force) {
    // 每秒 fsync
    if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
       // 有 fsync 正在后臺進行 
        if (sync_in_progress) {
            if (server.aof_flush_postponed_start == 0) {
                /*
                 * 前面沒有推遲過 write 操作,將推遲寫操作的時間記錄下來
                 * 不執行 write 或者 fsync
                 */
                server.aof_flush_postponed_start = server.unixtime;
                return;
            } else if (server.unixtime - server.aof_flush_postponed_start < 2) {
                /*
                 * 如果之前已經因為 fsync 而推遲了 write 操作
                 * 但是推遲的時間不超過 2 秒
                 * 不執行 write 或者 fsync
                 */
                return;
            }
            / *
             * 如果后臺還有 fsync 在執行,并且 write 已經推遲 >= 2 秒
             * 那么執行寫操作
             */
            redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
        }
    }
    /*
     * 執行到這里,程序會對 AOF 文件進行寫入。
     */
    nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
         }
}

從上面的流程可以看出,當有bio線程運行fsync,就會推遲write(aof_buf)
推遲超過2s之后,不管是否有bio線程運行fsync,都會直接調用wirte
這個時候,如果fsync正在執行的的話,就會導致write阻塞,Redis服務也就阻塞了。
這里的IO阻塞跟IO繁忙無關,只是因為fsyncwrite都在操作同一個fd

When the fsync policy is set to 'everysec' we may delay the flush if there  is still an fsync() going on in the background thread, since for instance on Linux write(2) will be blocked by the background fsync anyway.

題外話:那么為什么要將fsync放入后臺線程中執行呢?

比如,在SSD上連續寫1G,如果每次寫入4k,就使用fsync刷page cache的話,需要20+min才能執行完成。
而如果所有1G先write再調用fsync()刷盤,2s就寫成功了。
時間相差600倍。可見頻繁fsync會導致redis性能大大降低。

2.重寫aof日志:

其中有三處可能會出現阻塞:
1)將累積的aof_rewrite_bufwritetmpfile文件中
2)rename tmpfileaof

  1. 將1)中write的緩存,fsync到磁盤中
    4)刪除舊aof文件

2.1 對于3)fysnc

是因為fsync本身是一個耗時的操作,所以放入bio線程中執行。

2.2 對于2)、4)

作者將close放入bio線程中執行。這里比較難理解,需要我們理解close,unlink,rename和文件刪除的關系。

先來看下linux man中說明(只討論文件):
Close:關閉文件描述符

a)調用過unlink(使得鏈接數為0),且close的這個fd是對該file最后一個引用,會觸發文件的刪除。
if the file descriptor was the last reference to a file which has been removed using unlink(2), the file is deleted.
b)當調用close的時候,緩存pagecache并不會刷入磁盤。
Typically, filesystems do not flush buffers when a file is closed.  If you need to be sure that the data is physically stored on the underlying disk, use fsync(2). 

所以說如果close發生了阻塞,應該就是close觸發了文件刪除。

Unlink:刪除目錄項(i節點),并將pathname所引用文件鏈接數(硬鏈接)計數減一。

int unlink(const char *pathname)
只有當鏈接數到達0,文件內容才可能被刪除。但是,當有進程打開這個文件,文件內容也不能被刪除。

所以說:
a)close本身并不會刪除文件,除非之前調用過unlink。使得引用數和鏈接數都為0。
b)unlink本身也不會刪除文件,除非此時引用數和鏈接為0。
只有引用數和鏈接數都為0,closeunlink才會刪除文件,導致阻塞。

Rename:

int rename(const char *oldname, const char *newname) 
Rename這個系統調用會unlink newname對應的文件,然后將舊文件的名字改成新名字。
涉及到刪除文件,遵守上文所述刪除文件規則,也就是說rename也不一定會真正刪除文件。
If the link named by the new argument exists, it shall be removed and old renamed to new.

所以2)中rename tmpfile時,由于fd仍然被引用,并不會真正的刪除文件。到4)時,調用close,才會真正刪除文件。由于We don't want close(2) or rename(2) calls to block the server on old file deletion.此時將close放入bio線程中執行,避免服務阻塞。

2.3 對于1)

其實這種情況在開始aofredis實例中并不少見。

以下為一個故障現象整理:

子進程做aof,對redis資源消耗。此時redis服務正常。
a)aof rewrite耗時20:04:41-20:26:41共9分鐘
b)aof_rewrite_buf使用9280M
c)用戶大部分時間平均每秒寫入10M/S,高峰寫入50M/S
子進程生成新aof,主進程將aof_rewrite_buf寫入aof文件中。
此時redis阻塞,不相應外部服務
耗時:20:26:42-20:29:51共3分11秒。

正常情況下,這應該是秒級別就完成的操作。
之所以阻塞,是由頁回寫機制造成的,我們有兩個方向可以嘗試解決這個問題:

2.3.1 在程序層面,將集中write改成多次頻繁寫。

redis4.0利用管道優化aofwrite,具體可參見 https://yq.aliyun.com/articles/177819

2.3.2 系統層面調優,優化內核參數,將寫活動高峰分布成頻繁的多次寫。

首先我們需要了解,臟頁是什么時候回寫的:

a)空閑內存低于閾值:/proc/sys/vm/dirty_background_ratio 
vm.dirty_background_ratio is the percentage of system memory that can be filled with dirty pages — memory pages that still need to be written to disk — before the pdflush/flush/kdmflush background processes kick in to write it to disk.

b)臟頁在內存中駐留的時間超過一個特定的閾值:/proc/sys/vm/dirty_expire_centisecs
When the pdflush/flush/kdmflush processes kick in they will check to see how old a dirty page is, and if it’s older than this value it’ll be written asynchronously to disk.

c)進程調用sync/fsync

d)進程調用write寫文件刷新緩存。
WRITE寫的時候,緩存超過dirty_ratio,則會阻塞寫操作,回刷臟頁,直到緩存低于dirty_ratio;如果緩存高于background_writeout,則會在寫操作時,喚醒pdflush進程刷臟頁,不阻塞寫操作。

注:
 在Linux-3.2新內核中,page cache和buffer cache的刷新機制發生了改變。放棄了原有的pdflush機制,改成了bdi_writeback機制。這種變化主要解決原有pdflush機制存在的一個問題:在多磁盤的系統中,pdflush管理了所有磁盤的page/buffer cache,從而導致一定程度的IO性能瓶頸。bdi_writeback機制為每個磁盤都創建一個線程,專門負責這個磁盤的pagecache或者buffer cache的數據刷新工作,從而實現了每個磁盤的數據刷新程序在線程級的分離,這種處理可以提高IO性能。
https://blog.csdn.net/younger_china/article/details/55187057

從d)中可以看出,當磁盤寫入繁忙,導致臟頁占用內存比率增大。此時調用wirte,fsync都會導致調用進程時間掛起。這也是前面aof write耗費3分11秒的原因。

針對上述臟頁回收時機,可以做如下參數調優:

減少內存使用 
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
最大化的使用內存 
vm.dirty_background_ratio = 50
vm.dirty_ratio = 80
優化寫入性能, 可以使用內存, 但是等到空閑的時候希望內存被回收, 比較經常用在應對突然有峰值的這種情況 
vm.dirty_background_ratio = 5
vm.dirty_ratio = 80

參見 https://mp.weixin.qq.com/s/9AwI6UfMTk3bcs1BlpfCYA

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • 一、Redis高可用概述 在介紹Redis高可用之前,先說明一下在Redis的語境中高可用的含義。 我們知道,在w...
    空語閱讀 1,606評論 0 2
  • 1.1 資料 ,最好的入門小冊子,可以先于一切文檔之前看,免費。 作者Antirez的博客,Antirez維護的R...
    JefferyLcm閱讀 17,087評論 1 51
  • 本文檔翻譯自http://redis.io/topics/persistence。 這篇文章提供了 Redis 持...
    daos閱讀 702評論 0 10
  • 今天來姐姐家玩,看到姐姐的朋友和姐姐兩個人為了自己娃娃的作業而心力交瘁的樣子我有些不知所措 侄兒上小學二年級,我記...
    鑫心兒閱讀 584評論 0 0
  • 與姝會友閱讀 129評論 0 0