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

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

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

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

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

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

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

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

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

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

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

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

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

07 總結(jié)
本文主要講解 Redis AOF 、RDB 持久化的原理和兩者的優(yōu)缺點,對比兩者后,我還給你總結(jié)了 Redis 混合持久化的流程。最后給了你一些選擇持久化方案的建議,希望看完你能有所收獲。
全文將近字,張圖,希望能幫到你。好啦,以上就是狗哥關(guān)于 Redis 持久化的總結(jié)。感謝各技術(shù)社區(qū)大佬們的付出,尤其是極客時間,真的牛逼。如果說我看得更遠(yuǎn),那是因為我站在你們的肩膀上。
巨人的肩膀
《Redis 設(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,瑞出事來了!

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