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