redis AOF 性能瓶頸分析
最近發現一個問題,redis 在高流量寫入的情況下,偶發性出現客戶端延遲升高,經過排查發現 redis AOF 重寫 fork 子進程導致。爲什麼要進行 AOF 重寫,以及如何避免 AOF 重寫呢?本文做個介紹。
1. 什麼是 AOF
AOF 是 redis 防止數據丟失的日誌備份策略,總共有三種方式
-
Always 同步寫回:每個寫命令執行完同步地將日誌寫回磁盤;可靠性高,數據基本不會丟失,但同時每次命令都需要寫到磁盤,性能影響比較大。除非對於數據丟失非常敏感,否則不會選擇這種策略。
-
Everysec 每秒寫回:每個寫命令執行完,只是先把日誌寫到 AOF 文件的內存緩衝區,每隔一秒把緩衝區中的內容寫入磁盤;首先異步寫到緩衝區,redis 會使用單獨的線程每秒寫回到磁盤,如果這期間出現宕機,可能會丟失 1s 左右的數據,但是性能得到了保證。相當於是性能和數據丟失之間做了一個折衷,這個也是默認策略。
-
No 操作系統控制的寫回:每個寫命令執行完,只是先把日誌寫到 AOF 文件的內存緩衝區,由操作系統決定何時將緩衝區內容寫回磁盤。由操作系統控制何時寫會,性能非常好;如果發生宕機,也會造成大量數據丟失。
說到 AOF,其實很多人都會拿它跟 Rdb 去做比較,Rdb 是以二進制的方式存儲到磁盤上。體積更小,當出現服務重啓或者宕機,相對於 AOF 恢復速度更快。
另外一點,RDB 和 AOF 對客戶端的寫入性能影響,一般情況下,AOF 的寫入性能是比不上 RDB 的,因爲 AOF 多了一個寫入操作,但是隨着寫入數據量越來越大,這個差距會越來越小。看完本文後,你應該能夠找到答案。具體可以參考 redis 官網https://redis.io/topics/persistence
,這裏不過多贅述。
2. AOF 重寫又是怎麼回事
很多同學對 redis 的寫 AOF 文件和 AOF 重寫傻傻分不清,無論是發生時機或者操作對象其實是沒有任何關係的。
2.1. 寫 AOF 文件
寫 AOF 文件發生在客戶端請求 redis server,這個時候就會產生一條 AOF 記錄,這條記錄何時寫入磁盤跟自身設置的 AOF 策略控制相關,可以同步、也可以異步寫入。
2.2. AOF 重寫操作
如果 redis server 接受的寫請求越來越多,那麼 AOF 文件會越來越大,爲了防止 AOF 文件無限膨脹(打爆磁盤)以及不利於 redis server 宕機後的恢復,所以要進行重寫。其實說白了就是一個重複命令合併的過程。
對於上圖幾個關鍵點:
-
1、在重寫期間,由於主進程依然在響應命令,爲了保證最終備份的完整性;因此它依然會寫入舊的 AOF file 中,如果重寫失敗,能夠保證數據不丟失。
-
2、爲了把重寫期間響應的寫入信息也寫入到新的文件中,因此也會爲子進程保留一個 buf,防止新寫的 file 丟失數據。如果主進程收到了寫請求,子進程和父進程是通過管道的方式進行發送這個期間發生的請求,所以這個期間寫操作並不會丟失。
-
3、重寫是直接把當前內存的數據生成對應命令,並不需要讀取老的 AOF 文件,最後通過 rename 完成文件的替換工作。
2.3. AOF 重寫發生條件。
-
1、開啓 AOF
-
2、沒有 RDB 和 AOF 進程運行
-
3、auto-aof-rewrite-min-size:AOF 文件大小絕對值的最小值,默認爲 64MB,具體見 redis.conf。auto-aof-rewrite-percentage:AOF 文件大小超出基礎大小的比例,默認值爲 100%,即超出 1 倍大小。
如下是源碼所示:
//如果AOF功能啓用、沒有RDB子進程和AOF重寫子進程在執行、AOF文件大小比例設定了閾值,以及AOF文件大小絕對值超出了閾值,進一步判斷AOF文件大小比例是否超出閾值
if (server.aof_state == AOF_ON &&
server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
server.aof_rewrite_perc
&& server.aof_current_size > server.aof_rewrite_min_size)
{.....}
看到這裏,再想想,爲什麼 redis 之所以添加各種條件限制 AOF 的發生?
儘可能減少 CPU 和 IO 消耗
3. 如何避免 AOF 造成的影響
3.1. 影響原因
上文中也說了,AOF 主要耗時發生在 fork 一個子進程並且會阻塞主進程,這是爲什麼呢?
因爲 fork 子進程時,子進程是會拷貝父進程的頁表,即虛實映射關係,但是 fork 不會把所有的內存數據都 copy 到子進程,只會 copy 一部分有用的數據到子進程中。
所以 fork 在複製內存頁的時候會大量的消耗 CPU 資源,如果複製的內存頁越大,fork 阻塞的時間就會越久。拷貝內存頁完成,子進程與父進程指向相同的內存地址,這個時候就會放開主進程的阻塞, 對外提供操作。每當有新的寫命令,就會觸發操作系統的 COW 寫時複製機制,此時就會把這新的命令寫到 AOF 日誌緩衝區,等待數據重寫完成後,重寫的日誌與緩衝區修改的數據進行合併,這樣保證了父子進程之間的數據同步。
3.1. fork 導致阻塞時長
正常情況 fork 耗時應該是每 GB 消耗 20ms 左右,當然也可以用 info stats 命令查看 latest_fork_usec 指標, 獲取最近一次 fork 操作耗時, 單位微秒。
3.2. 如何避免
-
調整 AOF 觸發條件,比如從原來的 64 M,根據實際情況調大,降低 AOF 發生;
-
減少單 redis 實例大小,儘可能降低到 10G 以內,越小相應 fork 速度越快;
-
使用主從節點,AOF 發生在從節點,從而對讀寫的主節點沒有影響
-
linux 內核優化,禁止使用:echo never > /sys/kernel/mm/transparent_hugepage/enable,如果父進程有大量的內存頁寫入,就證明你的子進程內存開銷比較大,因爲它會寫內存副本,造成很大的內存開銷;
-
升級硬件,比如使用更好的 CPU,從機械硬盤換成 SSD;
總的來說,沒有好不好,只有是否合適。軟件系統的設計都是偏向於解決某個領域的問題,具體情況要看具體使用場景,比如可以考慮關閉 AOF,當服務流量低峯時手動觸發 AOF。也可以從自身的業務出發儘可能減少寫請求。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/b2_1cyasD1bTm5G9PsCIpQ