小伙用 12 張圖講明白了 Redis 持久化!
00 前言
很多小伙伴都用 Redis 做緩存,那如果 Redis 服務(wù)器宕機(jī),內(nèi)存中數(shù)據(jù)全部丟失,應(yīng)該如何做數(shù)據(jù)恢復(fù)呢?有人說(shuō)很簡(jiǎn)單呀,直接從 MySQL 數(shù)據(jù)庫(kù)再讀回來(lái)就得了。
這種方式存在兩個(gè)問(wèn)題:一是頻繁訪(fǎng)問(wèn)?MySQL 數(shù)據(jù)庫(kù),有一定的風(fēng)險(xiǎn);二是慢,從界面上來(lái)看,從 MySQL 讀就不如從 Redis 快。
遠(yuǎn)哥遠(yuǎn)哥,那咋辦呀?教教我吧。
我用中指抵著小胖的下吧,說(shuō)到:傻瓜,我們可以做持久化呀。Redis 的持久化分兩種,一種是 AOF,另一種是 RDB。來(lái),坐哥哥腿上,我給你好好說(shuō)道說(shuō)道。
老規(guī)矩,先上張腦圖:

0.1 什么是持久化?
持久化(Persistence),即把數(shù)據(jù)(如內(nèi)存中的對(duì)象)保存到可永久保存的存儲(chǔ)設(shè)備中(如磁盤(pán))。持久化的主要應(yīng)用是將內(nèi)存中的對(duì)象存儲(chǔ)在數(shù)據(jù)庫(kù)中,或者存儲(chǔ)在磁盤(pán)文件中、XML 數(shù)據(jù)文件中等等。持久化是將程序數(shù)據(jù)在持久狀態(tài)和瞬時(shí)狀態(tài)間轉(zhuǎn)換的機(jī)制
01 怎么理解 Redis 的單線(xiàn)程?
必須聲明一點(diǎn):Redis 的單線(xiàn)程,是指?Redis 的網(wǎng)絡(luò) IO 和鍵值對(duì)讀寫(xiě)是由一個(gè)線(xiàn)程(主線(xiàn)程)完成的,這也是 Redis 對(duì)外提供鍵值存儲(chǔ)服務(wù)的主要流程。但 Redis 的其他功能,比如持久化、異步刪除、集群數(shù)據(jù)同步等,其實(shí)是由額外的線(xiàn)程執(zhí)行的。
1.0 Redis 快的原因?
基于內(nèi)存
數(shù)據(jù)都存儲(chǔ)在內(nèi)存里,減少了一些不必要的 I/O 操作,操作速率很快。
高效的數(shù)據(jù)結(jié)構(gòu)
底層多種數(shù)據(jù)結(jié)構(gòu)支持不同的數(shù)據(jù)類(lèi)型,支持 Redis 存儲(chǔ)不同的數(shù)據(jù); 不同數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),使得數(shù)據(jù)存儲(chǔ)時(shí)間復(fù)雜度降到最低。
合理的線(xiàn)程模型
I/O 多路復(fù)用模型同時(shí)監(jiān)聽(tīng)多個(gè)客戶(hù)端連接;
單線(xiàn)程在執(zhí)行過(guò)程中不需要進(jìn)行上下文切換,減少了耗時(shí)。
02 AOF 持久化
AOF(Append Only File) 持久化是通過(guò)保存 Redis 服務(wù)器所執(zhí)行的寫(xiě)命令來(lái)記錄數(shù)據(jù)庫(kù)狀態(tài),也就是每當(dāng) Redis 執(zhí)行一個(gè)改變數(shù)據(jù)集的命令時(shí)(比如 SET), 這個(gè)命令就會(huì)被追加到 AOF 文件的末尾。
修改 redis.conf 配置文件,默認(rèn)是 appendonly no(關(guān)閉狀態(tài)),將 no 改為 yes 即可開(kāi)啟 AOF 持久化:
appendonly?yes
在客戶(hù)端輸入如下命令也可,但是 Redis 服務(wù)器重啟后會(huì)失效。
192.168.17.101:6379>?config?set?appendonly?yes
OK
AOF 持久化功能的實(shí)現(xiàn)可以分為命令追加(append)、文件寫(xiě)回磁盤(pán)兩個(gè)步驟。
2.0 命令追加
AOF 持久化功能開(kāi)啟時(shí),Redis 在執(zhí)行完一個(gè)寫(xiě)命令之后,會(huì)將被執(zhí)行的寫(xiě)命令追加到服務(wù)器狀態(tài)的?aof_buf 緩沖區(qū)的末尾,此時(shí)緩沖區(qū)的記錄還沒(méi)有寫(xiě)入到 appendonly.aof 文件中。
2.0.1 AOF 的格式
AOF 保存的是 Redis 的寫(xiě)命令,比如:執(zhí)行命令?set testkey testvalue,它存儲(chǔ)的內(nèi)容如下圖所示:

其中,“*3” 表示當(dāng)前命令有三個(gè)部分,每部分都是由?$+ 數(shù)字開(kāi)頭,后面緊跟著具體的命令、鍵或值。這里,數(shù)字表示這部分中的命令、鍵或值一共有多少字節(jié)。例如,?$3 set?表示這部分有 3 個(gè)字節(jié),也就是?set?命令。
2.0.2 寫(xiě)后日志有啥優(yōu)缺點(diǎn)?
AOF 記錄日志的方式被稱(chēng)為寫(xiě)后日志,也就是先執(zhí)行命令再記錄,而 MySQL 中的 redo log、binlog 等都是寫(xiě)前日志。它的寫(xiě)入流程是下圖這樣的:

寫(xiě)后有什么優(yōu)點(diǎn)?
記錄 AOF 時(shí)不會(huì)對(duì)命令進(jìn)行語(yǔ)法檢查 ,寫(xiě)后就只記錄了執(zhí)行成功的命令。(避免保存的錯(cuò)誤的命令,恢復(fù)的時(shí)候就完?duì)僮恿耍?/section> 執(zhí)行完之后再記錄,不會(huì)阻塞當(dāng)前的寫(xiě)操作
寫(xiě)后有什么缺陷?
如果執(zhí)行完一個(gè)命令還沒(méi)來(lái)得及寫(xiě)日志就宕機(jī)了會(huì)造成響應(yīng)數(shù)據(jù)丟失。 AOF 的寫(xiě)入由主線(xiàn)程處理,如果寫(xiě)入時(shí)出現(xiàn)較長(zhǎng)耗時(shí),那就會(huì)影響主線(xiàn)程處理后續(xù)的請(qǐng)求。
你發(fā)現(xiàn)沒(méi)有?寫(xiě)后的兩個(gè)缺陷都是 AOF 的寫(xiě)入磁盤(pán)時(shí)相發(fā)生的,我們來(lái)看看它是怎么寫(xiě)入的呢?
2.1 AOF 寫(xiě)入磁盤(pán)
AOF 提供了三個(gè)選擇,也就是 AOF 配置項(xiàng)?appendfsync?的三個(gè)可選值。
Always,同步寫(xiě)回:每個(gè)寫(xiě)命令執(zhí)行完,立馬同步地將日志寫(xiě)回磁盤(pán);
Everysec(默認(rèn)),每秒寫(xiě)回:每個(gè)寫(xiě)命令執(zhí)行完,只是先把日志寫(xiě)到 AOF 文件的內(nèi)存緩沖區(qū),每隔一秒把緩沖區(qū)中的內(nèi)容寫(xiě)入磁盤(pán);
No,操作系統(tǒng)控制的寫(xiě)回:每個(gè)寫(xiě)命令執(zhí)行完,只是先把日志寫(xiě)到 AOF 文件的內(nèi)存緩沖區(qū),由操作系統(tǒng)決定何時(shí)將緩沖區(qū)內(nèi)容寫(xiě)回磁盤(pán)。
2.1.0 三種策略的優(yōu)缺點(diǎn)
針對(duì)避免主線(xiàn)程阻塞和減少數(shù)據(jù)丟失問(wèn)題,這三種寫(xiě)回策略都無(wú)法做到兩全其美。主要原因是:
Always(同步寫(xiě)回) 基本不丟數(shù)據(jù),但是它在每一個(gè)寫(xiě)命令后都有一個(gè)慢速的落盤(pán)操作,影響主線(xiàn)程性能; No(操作系統(tǒng)控制的寫(xiě)回)在寫(xiě)完緩沖區(qū)后,繼續(xù)執(zhí)行后續(xù)的命令,但是落盤(pán)的時(shí)機(jī)已經(jīng)不在 Redis 手中了,只要 AOF 記錄沒(méi)有寫(xiě)回磁盤(pán),一旦宕機(jī)對(duì)應(yīng)的數(shù)據(jù)就丟失了; Everysec(每秒寫(xiě)回)采用一秒寫(xiě)回一次的頻率,避免了 Always 的性能開(kāi)銷(xiāo),雖然減少了對(duì)系統(tǒng)性能的影響,但是如果發(fā)生宕機(jī),上一秒內(nèi)未落盤(pán)的命令操作仍然會(huì)丟失。

總結(jié)一下就是:想高性能,選擇 No 策略;想高可靠性,選擇 Always 策略;允許數(shù)據(jù)有一點(diǎn)丟失,又希望性能別受太大影響,選擇 Everysec 策略。
2.2 AOF 恢復(fù)數(shù)據(jù)
不說(shuō)了,看圖:

2.3 AOF 重寫(xiě)
我不知道你發(fā)現(xiàn)沒(méi)有?AOF 文件是不斷地將寫(xiě)命令追加到文件的末尾來(lái)記錄數(shù)據(jù)庫(kù)狀態(tài)的。寫(xiě)命令不斷增加,AOF 體積也越來(lái)越大。
有些命令是執(zhí)行多次更新同一條數(shù)據(jù),但其實(shí)它是可以合并成同一條命令的。比如:LPUSH 對(duì)列表數(shù)據(jù)做了 6 次更改,但 AOF 只需要記錄最后一次更改。因?yàn)槿罩净謴?fù)時(shí),只需要執(zhí)行最后一次更改的命令即可。
為了處理這種情況,Redis 提供了 AOF 的重寫(xiě)機(jī)制。它的多變一功能,把 6 條寫(xiě)命令合并成一條。如下所示:

如果你的某些鍵有成百上千次的修改,重寫(xiě)機(jī)制節(jié)約的空間就很可觀(guān)了。
2.3.1 觸發(fā)重寫(xiě)
有兩種觸發(fā)的方法,一個(gè)是調(diào)用命令 BGREWRITEAOF;一個(gè)是修改配置文件參數(shù)。
#?方式一
192.168.17.101:6379>?BGREWRITEAOF
Background?append?only?file?rewriting?started
#?方式二
auto-aof-rewrite-percentage?100?#當(dāng)前AOF文件大小和上一次重寫(xiě)時(shí)AOF文件大小的比值
auto-aof-rewrite-min-size?64mb??#文件的最小體積
2.3.2 重寫(xiě)步驟
創(chuàng)建子進(jìn)程進(jìn)行 AOF 重寫(xiě) 將客戶(hù)端的寫(xiě)命令追加到 AOF 重寫(xiě)緩沖區(qū) 子進(jìn)程完成 AOF 重寫(xiě)工作后,會(huì)向父進(jìn)程發(fā)送一個(gè)信號(hào) 父進(jìn)程接收到信號(hào)后,將 AOF 重寫(xiě)緩沖區(qū)的所有內(nèi)容寫(xiě)入到新 AOF 文件中 對(duì)新的 AOF 文件進(jìn)行改名,覆蓋現(xiàn)有的 AOF 文件

2.4 相關(guān)配置
#?是否開(kāi)啟AOF功能
appendonly?no
#?AOF文件件名稱(chēng)
appendfilename?"appendonly.aof"
#?寫(xiě)入AOF文件的三種方式
appendfsync?always
appendfsync?everysec
appendfsync?no
#?重寫(xiě)AOF時(shí),是否繼續(xù)寫(xiě)AOF文件
no-appendfsync-on-rewrite?no
#?自動(dòng)重寫(xiě)AOF文件的條件
auto-aof-rewrite-percentage?100?#百分比
auto-aof-rewrite-min-size?64mb?#大小
#?是否忽略最后一條可能存在問(wèn)題的指令
aof-load-truncated?yes
2.5 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
AOF 文件可讀性高,分析容易 AOF 文件過(guò)大時(shí),自動(dòng)進(jìn)行重寫(xiě) 追加形式,寫(xiě)入時(shí)不需要再次讀取文件,直接加到末尾
缺點(diǎn)
相同數(shù)據(jù)量下,AOF 一般比 RDB 大 AOF 恢復(fù)時(shí)需要重放命令,恢復(fù)速度慢 根據(jù) fsync 策略,AOF 的速度可能慢于 RDB
03 RDB 持久化
RDB 持久化是指在客戶(hù)端輸入?save、bgsave?或者達(dá)到配置文件自動(dòng)保存快照條件時(shí),將 Redis 在內(nèi)存中的數(shù)據(jù)生成快照保存在名字為?dump.rdb(文件名可修改)的二進(jìn)制文件中。
3.1 save 命令
save 命令會(huì)阻塞 Redis 服務(wù)器進(jìn)程,直到 RDB 文件創(chuàng)建完畢為止,在 Redis 服務(wù)器阻塞期間,服務(wù)器不能處理任何命令請(qǐng)求。在客戶(hù)端輸入 save
127.0.0.1:6379>?save
OK
快照生成完畢,會(huì)彈出?DB saved ondisk?的提示。
1349:M?25?Apr?13:16:48.935?*?DB?saved?on?disk
3.2 bgsave 命令
bgsave 執(zhí)行時(shí),主線(xiàn)程會(huì)創(chuàng)建一個(gè)子進(jìn)程,專(zhuān)門(mén)用于寫(xiě)入 RDB 文件,避免了主線(xiàn)程的阻塞,這也是 Redis RDB 文件生成的默認(rèn)配置。
127.0.0.1:6379>?bgsave
Background?saving?started
PS:bgsave 命令執(zhí)行期間 SAVE 命令會(huì)被拒絕;不能同時(shí)執(zhí)行兩個(gè) BGSAVE 命令;不能同時(shí)執(zhí)行 BGREWRITEAOF 和 BGSAVE 命令。
3.3 bgsave 時(shí)寫(xiě)數(shù)據(jù)
bgsave 執(zhí)行時(shí),Redis 主線(xiàn)程能正常讀寫(xiě)數(shù)據(jù)。讀操作時(shí),主線(xiàn)程和 bgsave 子線(xiàn)程互不影響;寫(xiě)操作時(shí),Redis 會(huì)利用寫(xiě)時(shí)復(fù)制技術(shù)(Copy-On-Write, COW),生成被修改數(shù)據(jù)的副本。然后 bgsave 子線(xiàn)程把副本數(shù)據(jù)寫(xiě)入 RDB。
比如,bgsave 期間,主線(xiàn)程修改鍵值對(duì) C,過(guò)程如下:

但是在這過(guò)程中發(fā)生宕機(jī)了咋辦?比如,T0 時(shí)刻做了一次快照,T0+t 時(shí)刻又做了一次。但是 t 時(shí)間內(nèi)主線(xiàn)程修改完數(shù)據(jù) 5 和 9,然后 Redis 宕機(jī)了,RDB 沒(méi)記錄到修改后的數(shù)據(jù)。
Redis 重啟恢復(fù)數(shù)據(jù),就會(huì)出現(xiàn)數(shù)據(jù) 5 和 9 丟失的情況,沒(méi)辦法恢復(fù)。

這該咋辦?我們需要記住那些數(shù)據(jù)被修改了。
3.4 混合持久化
如下圖所示,記錄 t 時(shí)刻被修改的數(shù)據(jù)就需要占用額外的空間,而 Redis 是內(nèi)存數(shù)據(jù)庫(kù),空間非常寶貴。所以,直接記錄到內(nèi)存這種方式不可取。

內(nèi)存開(kāi)銷(xiāo)比較小的方法是把 t 時(shí)間的增量寫(xiě)操作記錄到 AOF 日志中,這樣既保留了 RDB 的快速恢復(fù),也沒(méi)占用額外的空間。
如圖,T1 和 T2 時(shí)刻的修改,用 AOF 日志記錄,等第二次做全量快照時(shí),清空 AOF 日志,因?yàn)榇藭r(shí)的修改都記錄到快照中了,恢復(fù)不用 AOF 日志了。

慶幸的是 Redis 4.0 就開(kāi)始提供了這種?RDB + AOF 的持久化方式,開(kāi)啟的配置項(xiàng)是?aof-use-rdb-preamble yes,它需要配合 AOF 的重寫(xiě)機(jī)制實(shí)現(xiàn)。
#?開(kāi)啟混合持久化
redis>?config?set?aof-use-rdb-preamble?yes
OK
#?AOF?重寫(xiě)
redis>?BGREWRITEAOF
Background?append?only?file?rewriting?started
在沒(méi)有第二次做全量快照之前,它的格式是這樣的:前半部分是 RDB 格式,后半部分是 AOF 增量日志。如果這個(gè)時(shí)候宕機(jī),直接拿 appendonly.aof 恢復(fù)數(shù)據(jù)。

3.5 RDB 優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
二進(jìn)制數(shù)據(jù),恢復(fù)時(shí)比 AOF 快 RDB 的 bgsave 方式主線(xiàn)程不阻塞
缺點(diǎn)
Redis 意外宕機(jī) 時(shí),會(huì)丟失部分?jǐn)?shù)據(jù)(混合持久化可解決) 當(dāng)數(shù)據(jù)量比較大時(shí),fork 的過(guò)程是非常耗時(shí)的,fork 子進(jìn)程時(shí)是會(huì)阻塞的,在這期間 Redis 是不能響應(yīng)客戶(hù)端的請(qǐng)求的。
04 如何選擇?
數(shù)據(jù)不能丟失時(shí),選擇內(nèi)存快照和 AOF 混合使用; 如果允許分鐘級(jí)別的數(shù)據(jù)丟失,可以只使用 RDB; 如果只用 AOF,優(yōu)先使用 everysec 的配置選項(xiàng),因?yàn)樗诳煽啃院托阅苤g取了一個(gè)平衡。
05 數(shù)據(jù)恢復(fù)流程

07 總結(jié)
本文主要講解 Redis AOF 、RDB 持久化的原理和兩者的優(yōu)缺點(diǎn),對(duì)比兩者后,我還給你總結(jié)了 Redis 混合持久化的流程。最后給了你一些選擇持久化方案的建議,希望看完你能有所收獲。
全文將近字,張圖,希望能幫到你。好啦,以上就是狗哥關(guān)于 Redis 持久化的總結(jié)。感謝各技術(shù)社區(qū)大佬們的付出,尤其是極客時(shí)間,真的牛逼。如果說(shuō)我看得更遠(yuǎn),那是因?yàn)槲艺驹谀銈兊募绨蛏稀?/p>
巨人的肩膀
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》 juejin.cn/post/6844903648976175118 blog.csdn.net/weixin_33810006/article/details/90394921 blog.csdn.net/wsdc0521/article/details/106765809
完
往期推薦

7000 字,四年多 Java 的 BAT 面經(jīng)分享!

代碼review,瑞出事來(lái)了!

synchronized 底層了解一下...
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
