面試中經常被問到的Redis持久化與恢復,該如何解決?
一、前言
本文主要講了 Redis 的持久化相關功能,持久化一直是影響 Redis 性能的高發地,也是面試中經常被問到的。
包括 RDB 相關的特定和優缺點,AOF 的優缺點,事實上,由于 RDB 的數據實時性問題,目前用 AOF 比較多了,而持久化恢復也是優先 AOF。
RDB 是舊的模式,現在基本上都使用 AOF,當然,今天兩個都會一起聊聊。
二、 RDB
RDB 流程圖:
RDB 特點:
1、RDB 是一種快照模式,即——保存的是 key value 數據內容。
2、RDB 有 2 種持久方式,同步 save 模式和異步 bgsave 模式。由于 save 是同步的,所以可以保證數據一致性,而 bgsave 則不能。
3、save 可以在客戶端顯式觸發,也可以在 shutdown 時自動觸發;bgsave 可以在客戶端顯式觸發,也可以通過配置由定時任務觸發,也可以在 slave 節點觸發。
4、save 導致 redis 同步阻塞,基本已經廢棄。bgsave 則不會導致阻塞,但也有缺點:在 fork 時,需要增加內存服務器開銷,因為當內存不夠時,將使用虛擬內存,導致阻塞 Redis 運行。所以,需要保證空閑內存足夠。
5、默認執行 shutdown 時,如果沒有開啟 AOF,則自動執行 bgsave。
6、每次的 RDB 文件都是替換的。
關于優化:
Redis 會壓縮 RDB 文件,使用 LZF 算法,讓最終的 RDB 文件遠小于內存大小,默認開啟。但會消耗 CPU。
RDB 缺點:
1、無法秒級持久化。
2、老版本 Redis 無法兼容新版本 RDB。
RDB 優點:
1、文件緊湊,適合備份,全量復制場景。例如每 6 小時執行 bgsave,保存到文件系統之類的。
2、Redis 加載 RDB 恢復數據遠遠快于 AOF。
三、 AOF
由于 RDB 的數據實時性問題,AOF(append only file) 是目前 Redis 持久化的主流方式。
AOF 特點:
1、默認文件名是 appendonly.aof。和 RDB 一樣,保存在配置中 dir 目錄下。
2、AOF 相比較于 RDB,每次都會保存寫命令,數據實時性更高。
3、AOF 由于每次都會記錄寫命令,文件會很大,因此需要進行優化,稱之為“重寫機制”(下面詳細說)。
4、AOF 每次保存的寫命令都放在一個緩沖區,根據不同的策略(下面詳細說)同步到磁盤。
“重寫機制” 細節:
1、fork 子進程(類似 bgsave)
2、主進程會寫到2個緩沖區,一個是原有的 “AOF 緩存區”,一個是專門為子進程準備的 “AOF 重寫緩沖區”;
3、子進程寫到到新的 AOF 文件中,批量的,默認 32m;寫完后通知主進程。
4、主進程把“AOF 重寫緩沖區”的數據寫到新 AOF 文件中。
5、將新的 AOF 文件替換老文件。
重寫流程圖:
緩沖區同步策略,由參數 appendfsync 控制,一共3種:
1、always:調用系統 fsync 函數,直到同步到硬盤返回; 嚴重影響redis性能。
2、everysec:先調用 OS write 函數, 寫到緩沖區,然后 redis 每秒執行一次 OS fsync 函數。 推薦使用這種方式。
3、no: 只執行 write OS 函數,具體同步硬盤策略由 OS 決定; 不推薦,數據不安全,容易丟失數據。
四、持久化恢復
AOF 和 RDB 文件都可以用于服務器重啟時的數據恢復,具體流程如下圖:
從圖中可以看出優先加載 AOF,當沒有 AOF 時才加載 RDB。當 AOF 或者 RDB 存在錯誤,則加載失敗。
五、問題排查和性能優化
Redis 持久化是影響 Redis 性能的高發地,也是面試中常問的問題。
1. fork 操作
當 Redis 做 RDB 或者 AOF 重寫時,必然要進行 fork 操作,對于 OS 來說,fork 都是一個重量級操作。而且,fork 還會拷貝一些數據,雖然不會拷貝主進程所有的物理空間,但會復制主進程的空間內存頁表。對于 10GB 的 Redis 進程,需要復制大約 20MB 的內存頁表,因此 fork 操作耗時跟進程總內存量息息相關,再加上,如果使用虛擬化技術,例如 Xen 虛擬機,fork 會更加耗時。
一個正常的 fork 耗時大概在 20毫秒左右。為什么呢,假設一個 Redis 實例的 OPS 在 5 萬以上,如果 fork 操作耗時在秒級,那么僵拖慢幾萬條命令的執行,對生產環境影響明顯。
我們可以在 Info stats 統計中查詢 latestforkusec 指標獲取最近一次 fork 操作耗時,單位微秒。
如何優化:
1、優先使用物理機或者高效支持 fork 的虛擬化技術,避免使用 Xen。
2、控制 redis 實例***內存,盡量控制在 10GB 以內。
3、合理配置 Linux 內存分配策略,避免內存不足導致 fork 失敗。
4、降低 fork 的頻率,如適度放寬 AOF 自動觸發時機,避免不必要的全量復制。
2. 子進程開銷
fork 完畢之后,會創建子進程,子進程負責 RDB 或者 AOF 重寫,這部分過程主要涉及到 CPU,內存,硬盤三個地方的優化。
1、CPU 寫入文件的過程是 CPU 密集的過程,通常子進程對單核 CPU 利用率接近 90%。 如何優化呢?既然是 CPU 密集型操作,就不要綁定單核 CPU,因為這樣會和父 CPU 進行競爭。同時,不要和其他 CPU 密集型服務不是在一個機器上。如果部署了多個 Redis 實例,盡力保證統一時刻只有一個子進程執行重寫工作。
2、內存 子進程通過 fork 操作產生,占用內存大小等同于父進程,理論上需要兩倍的內存完成持久化操作,但 Linux 有 copy on write 機制,父子進程會共享相同的物理內存頁,當父進程處理寫操作時,會把要修改的頁創建對應的副本,而子進程在 fork 操作過程中,共享整個父進程內存快照。 即——如果重寫過程中存在內存修改操作,父進程負責創建所修改內存頁的副本。這里就是內存消耗的地方。 如何優化呢?盡量保證同一時刻只有一個子進程在工作;避免大量寫入時做重寫操作。
3、硬盤 硬盤開銷分析:子進程主要職責是將 RDB 或者 AOF 文件寫入硬盤進行持久化,勢必對硬盤造成壓力,可通過工具例如 iostat,iotop 等,分析硬盤負載情況。
如何優化:
1、不要和其他高硬盤負載的服務放在一臺機器上,例如 MQ,存儲。
2、AOF 重寫時會消耗大量硬盤 IO,可以開啟配置 no-appendfsync-on-rewrite,默認關閉。表示在 AOF 重寫期間不做 fsync 操作。
3、當開啟 AOF 的 Redis 在高并發場景下,如果使用普通機械硬盤,每秒的寫速率是 100MB左右,這時,Redis 的性能瓶頸在硬盤上,建議使用 SSD。
4、對于單機配置多個 Redis 實例的情況,可以配置不同實例分盤存儲 AOF 文件,分攤硬盤壓力。
3. AOF 追加阻塞
當開啟 AOF 持久化時,常用的同步硬盤的策略是“每秒同步” everysec,用于平衡性能和數據安全性,對于這種方式,redis 使用另一條線程每秒執行 fsync 同步硬盤,當系統資源繁忙時,將造成 Redis 主線程阻塞。
流程圖如下:
通過上圖可以發現:everysec 配置最多可能丟失 2 秒數據,不是 1 秒;如果系統 fsync 緩慢,將會導致 Redis 主線程阻塞影響效率。
問題定位:
1、發生 AOF 阻塞時,會輸入日志。用于記錄 AOF fsync 阻塞導致拖慢 Redis 服務的行為。
2、每當 AOF 追加阻塞事件發生時,在 info Persistence 統計中,aofdelayedfsync 指標會累加,查看這個指標方便定位 AOF 阻塞問題。
3、AOF 同步最多運行 2 秒的延遲,當延遲發生時說明硬盤存在性能問題,可通過監控工具 iotop 查看,定位消耗 IO 的進程。
4. 單機多實例部署
Redis 單線程架構無法充分利用多核CPU,通常的做法是一臺機器上部署多個實例,當多個實例開啟 AOF 后,彼此之間就會產生CPU 和 IO 的競爭。
如何解決這個問題呢?
讓所有實例的 AOF 串行執行。
我們通過 info Persistence 中關于 AOF 的信息寫出 Shell 腳本,然后串行執行實例的 AOF 持久化。
整個過程如圖:
通過不斷判斷 AOF 的狀態,手動執行 AOF 重寫,保證 AOF 不會存在競爭。具體的 Shell 編寫以及 info 信息判斷,可以查看下圖:
六、總結
本文主要講了 Redis 的持久化相關功能,持久化一直是影響 Redis 性能的高發地,也是面試中經常被問到的。包括 RDB 相關的特定和優缺點,AOF 的優缺點,事實上,由于 RDB 的數據實時性問題,目前用 AOF 比較多了。而持久化恢復也是優先 AOF。
關于持久化的問題排查,就很麻煩了,但無非幾個方面,fork 耗時,子進程的 CPU,內存,硬盤開銷,AOF 的同步阻塞,單機多實例部署。
這些優化,可以通過前面寫的分析進行排查。