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