redis經典問題: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


本文分享自微信公眾號 - DBA隨筆(gh_acc2bbc0d447),作者:DBA隨筆

 

我們的場景結論,

1,使用的nfs文件系統 掛載的遠端存儲,存的aof文件。

2,盤遠端網絡存儲又不是ssd高性能io盤。屬於遠端網絡+慢io

3,設置的appendfsync everysec

4,redis再容器里

所有性能低的場景都碰上了,所以報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

本質是沒有,按最佳實踐,標准化的去部署。

 

01、問題描述

今天早晨遇到一個Redis的線上的問題,也算是一個Redis的經典問題了,這里記錄下分析和排查過程,希望對大家有所幫助。

問題背景:

業務側反饋服務有超時,10:30分左右出現了大量的IO timeout的報警

業務的監控圖脫敏后如下,縱坐標是延時的請求次數,可以看到,10:30分左右,突然飆升很多。

報警信息中詳細定位了報警的Redis端口和域名,可以確定到某一台服務器的IP地址上,那其實只要對這個特定的IP地址進行分析就好了。

02、分析定位過程

首先先查看對應的連接數,發現10:32分的時候,確實有連接數上升的問題:

再次查看Redis日志中發現有大量重復的告警內容:

24:S 16 Aug 10:32:03.357 * 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. 24:S 16 Aug 10:32:05.067 * 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. 24:S 16 Aug 10:32:39.058 * 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. 24:S 16 Aug 10:33:13.067 * 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. 24:S 16 Aug 10:33:47.064 * 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. 24:S 16 Aug 10:34:21.099 * 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. 

日志的告警時間在大概10:32分左右,時間點也基本能夠對得上。

翻譯一下上面的報警日志:似乎是異步AOF的fsync刷盤動作花費了太長的時間,導致Redis直接將AOF文件寫入到AOF buffer中,而不再等候fsync的返回,這個動作可能影響Redis的性能。

那既然是AOF寫入磁盤時候的問題,那我們先來看看磁盤的使用情況吧:

可以看到,在10:32分左右,磁盤的util值已經達到了100%了。而且注意到這台服務器的磁盤類型是HDD,本身性能就不好,在加沙灰姑娘高負荷的寫入,必定對Redis的性能產生影響。

到這里,其實我們已經可以給出一種解決方案了,那就是使用SSD來代替傳統的HDD。但是這種方案不是一下子就能實現的,需要有一個成本評估之類的過程。

03、源碼分析

Redis相比MySQL好的一點就是它的源碼量少,完全可以通過源碼來查看一個問題,今天我也嘗試着從源碼層面分析了一次:

打開IDE,全局搜索上述日志關鍵字,一下子就定位到了下面的函數,(代碼很長,不想看可以直接看結論):

-----------函數注釋----------- /* Write the append only file buffer on disk. * * Since we are required to write the AOF before replying to the client, * and the only way the client socket can get a write is entering when the * the event loop, we accumulate all the AOF writes in a memory * buffer and write it on disk using this function just before entering * the event loop again. * 函數的作用是:將AOF buffer的內容刷到磁盤上,一般情況下在回復客戶端響應之前需要將AOF寫入, 業務在事件循環結束的時候才能收到已經寫入數據的反饋,我們把所有的AOF寫入在一個內存buffer中, 然后使用這個函數將它寫入磁盤,然后開始進入下一個時間循環 * About the 'force' argument: * * 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. * When this happens we remember that there is some aof buffer to be * flushed ASAP, and will try to do that in the serverCron() function. * * However if force is set to 1 we'll write regardless of the background * fsync. */ ---------------函數-------------- #define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */ void flushAppendOnlyFile(int force) { ssize_t nwritten; int sync_in_progress = 0; mstime_t latency; if (sdslen(server.aof_buf) == 0) return; if (server.aof_fsync == AOF_FSYNC_EVERYSEC) sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0; if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) { /* With this append fsync policy we do background fsyncing. * If the fsync is still in progress we can try to delay * the write for a couple of seconds. */ if (sync_in_progress) { if (server.aof_flush_postponed_start == 0) { /* No previous write postponing, remember that we are * postponing the flush and return. */ * 前面沒有推遲過 write 操作,這里將推遲寫操作的時間記錄下來 * 然后就返回,不執行 write 或者 fsync server.aof_flush_postponed_start = server.unixtime; return; } else if (server.unixtime - server.aof_flush_postponed_start < 2) { /* We were already waiting for fsync to finish, but for less * than two seconds this is still ok. Postpone again. */ * 如果之前已經因為 fsync 而推遲了 write 操作 * 但是推遲的時間不超過 2 秒,那么直接返回 * 不執行 write 或者 fsync return; } /* Otherwise fall trough, and go write since we can't wait * over two seconds. */ * 如果后台還有 fsync 在執行,並且 write 已經推遲 >= 2 秒 * 那么執行寫操作(write 將被阻塞) server.aof_delayed_fsync++; serverLog(LL_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."); } } 

從代碼中分析得知,當Redis后台有fsync操作,並且等待超過了2s,就會阻塞write操作,此時Redis是不可寫入的,就會打印出這條log日志,觸發業務超時。

下面的情況可能會導致Redis的fsync阻塞2s:

1、如果開啟了appendfsync everysec的fsync策略,並且no-appendfsync-on-rewrite參數為no,則redis在做AOF重寫的時候,也會每秒將命令fsync到磁盤上,而此時Redis的寫入量大而磁盤性能較差,fsync的等待就會嚴重;

2、單純的寫入量大,大到磁盤無法支撐這個寫入。例如appendfsync參數的值是everysec,每秒進行一次fsync,而磁盤的性能很差。

那其實我們的例子,經過排查發現,僅僅是第二種情況,在10:32分左右,業務的量翻倍了,導致了fsync寫滿了磁盤的util,導致業務超時。

為什么超過2s,Redis就會阻塞寫入?

從上述代碼的注釋中,注意到有一句: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.

這里就需要了解下Linux write(2)數據寫緩存機制對Redis的影響,Redis采用IO多路復用,將網絡請求轉換成一個個事件,每個事件結束的時候,調用linux write(2)機制將數據寫入到操作系統內核的buffer,如果這個時候write(2)被阻塞,Redis就不能執行下一個事件。

Linux中規定,當一個文件執行write(2)時候,如果對同一個文件正在執行fdatasync(2), write(2)就會被阻塞住。.如果Redis正在進行AOF重寫或者RDB快照制作,由於要寫入臨時文件,則很有可能導致上述fdatasync(2)超時, write(2)就會被阻塞住,那么整個Redis也會被阻塞住。

針對這種情況,Redis中指定了一個緩解機制,當發現有fdatasync的時候,先不進行write,而直接將數據存儲在Redis自身的cache中,但是如果超過2s還是這樣,還是會繼續調用write,然后打印日志,將aof_delayed_fsync變量加一。

因此,對於appendonly=everysec這個刷盤策略下最嚴謹的說法是:Redis意外關閉會造成最多不超過2s的數據丟失。

04、解決方案

其實上文中已經提到了一種方案,就是利用SSD替換HDD磁盤,在你的寫入量很大的時候,這可能是一個能夠立竿見影的好方法。

當然,通過一些參數的簡單調整,也能夠一定程度上緩解這個問題:

方法一:配置修改

Redis在執行write的時候,由操作系統自身被動控制何時進行fsync。這里如果我們要主動觸發操作系統的fsync,可以設置操作系統級別的參數:

# 查看內存的臟頁字節大小,設置為0代表由系統自己控制何時調用fsync
sysctl -a | grep vm.dirty_bytes vm.dirty_bytes = 0 # 修改為一個小的值,例如32MB,達到這個數據量就fsync,讓操作系統fsync這個動作更頻繁一點,避免單次fsync太多數據,導致阻塞 echo "vm.dirty_bytes=33554432" >> /etc/sysctl.conf   

方法二:關閉RDB或者AOF(安全性換效率)

關閉RDB可減少RDB生成的時候,對磁盤的壓力,而關閉AOF則可以減少AOF刷盤對磁盤的壓力,從而讓Redis的性能達到極致。不過這個方法只能用在純緩存場景,如果有持久化的要求,則一般不靠譜,因為安全性大大降低,一旦宕機,則數據無法恢復。

一種折中的方案,是從庫開啟AOF,而主庫關閉AOF。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM