Redis 的持久化剖析,通俗易懂
關(guān)注我們,設(shè)為星標(biāo),每天7:30不見不散,架構(gòu)路上與您共享 回復(fù)"架構(gòu)師"獲取資源
Redis 是一個(gè)非關(guān)系型的內(nèi)存數(shù)據(jù)庫,使用內(nèi)存存儲(chǔ)數(shù)據(jù)是它能夠進(jìn)行快速存取數(shù)據(jù)的原因之一。
在實(shí)際應(yīng)用中,常有人提倡把 Redis 只作為一種能夠提高用戶體驗(yàn)的組件來使用, 也就是說即使 Redis 服務(wù)掛掉之后也要保證系統(tǒng)正常使用。不過,在很多系統(tǒng)中還是希望既能發(fā)揮 Redis 基于內(nèi)存快速存取的特性,又希望機(jī)器斷電或 Redis服務(wù)停止后數(shù)據(jù)不丟失。所以,才引出了 Redis 的持久化功能。
在許多技術(shù)文章中,提到 Redis 的持久化時(shí)往往都會(huì)直接拋出兩個(gè)名詞 RDB 和 AOF。然后接下來就是分別介紹這兩個(gè)名詞。當(dāng)然,如果要談 Redis 的持久化肯定避免不了講 RDB 和 AOF,但這是介紹持久化最恰當(dāng)?shù)姆绞絾幔窟@樣的文章是不是顯得有些生硬呢?所以,在嘗試弄明白一個(gè)事物的原理時(shí)一定要從頭到尾的思考它存在的意義?為了解決什么問題?采用了什么方式?達(dá)到了什么目的?自己有沒有其它的方案?這樣從問題的源頭切入,才能對這個(gè)事物理解的更加深刻,從而能夠更好的幫助自己進(jìn)行舉一反三。而不是人云亦云,對于一些知識僅僅是背誦下來,這種死記硬背下來的知識在腦海里的保質(zhì)期也是短的可憐。
在前面,我們已經(jīng)提到為什么需要引入持久化?簡單的來說持久化就是把內(nèi)存中的數(shù)據(jù)存儲(chǔ)到外存上,這樣服務(wù)停止后,當(dāng)再啟動(dòng)的時(shí)候就可以把外存的數(shù)據(jù)讀取到內(nèi)存中從而達(dá)到了不丟失數(shù)據(jù)的目的。
1、RDB
如果讓你設(shè)計(jì)一個(gè)持久化的方案,你會(huì)怎么做呢?(假裝絞盡腦汁… …)首先,我們可以使用一種簡單的策略,將 Redis 中所有的數(shù)據(jù)按照一定格式全部寫到磁盤上,即創(chuàng)建數(shù)據(jù)的快照文件。然后,你為了盡量保證不丟數(shù)據(jù)需要考慮使用實(shí)時(shí)寫還是定時(shí)寫,又或者用其它策略。其實(shí),現(xiàn)在的你已經(jīng)在嘗試著去實(shí)現(xiàn) RDB (Redis Database)持久化的機(jī)制了。所以,你看它其實(shí)并不難。萬丈高樓從地起,先從一個(gè)簡單的 idea 開始,逐漸去完善它,豐富它的過程便是解決問題的過程。例如用這種思路去學(xué)習(xí)計(jì)算機(jī)網(wǎng)絡(luò)也是同樣適用的,你可以給自己出一個(gè)問題“如何讓兩臺(tái)電腦進(jìn)行通信?”,自己想辦法解決這個(gè)問題的過程肯定會(huì)比在計(jì)算機(jī)網(wǎng)絡(luò)課堂上收獲的知識更多,也更牢固。
盡管不需要我們寫代碼來實(shí)現(xiàn) RDB 持久化,但是并不妨礙我們來思考一下假如讓我們來實(shí)現(xiàn)的話大概會(huì)遇到哪些問題?例如:什么時(shí)候生成數(shù)據(jù)快照?文件數(shù)據(jù)格式的定義?如果在主進(jìn)程中進(jìn)行持久化,阻塞客戶端的請求后會(huì)不會(huì)有影響?接下來,我們就看一下 RDB 是如何做的吧。
1.1基本命令
在 Redis 中,提供了兩個(gè) RDB 持久化的命令: SAVE 和 BGSAVE 。執(zhí)行 SAVE 時(shí),Redis 服務(wù)會(huì)停止處理任何客戶端的命令請求;執(zhí)行 BGSAVE 時(shí),Redis 服務(wù)則會(huì)創(chuàng)建一個(gè)子進(jìn)程,由子進(jìn)程來負(fù)責(zé)數(shù)據(jù)的持久化,而此時(shí) Redis 服務(wù)就可以正常處理客戶端的請求。
BGSAVE 解決了我們對于持久化時(shí)是否會(huì)影響 Redis 服務(wù)處理客戶端的請求的擔(dān)心。
1.2自動(dòng)間隔性保存
自動(dòng)間隔性保存,則解決了“什么時(shí)候生成數(shù)據(jù)快照?”的問題。在 Redis 的配置文件中我們可以寫入以下配置:
save 600 1
save 300 10
save 60 100
save 30 1000上面的配置表示,如果在 600 秒內(nèi)對數(shù)據(jù)庫進(jìn)行了 1 次修改,就執(zhí)行執(zhí)行一次 BGSAVE 命令;如果在 300 秒內(nèi)對數(shù)據(jù)庫進(jìn)行了 10 次修改,就執(zhí)行一次 BGSAVE 命令;以此類推。你可以根據(jù)你的業(yè)務(wù)場景,配置 save 的參數(shù),也不僅僅局限于 4 條配置。
實(shí)現(xiàn)原理
在 Redis 啟動(dòng)時(shí),會(huì)把上述配置存儲(chǔ)到 Redis 服務(wù)器的狀態(tài)中,具體的結(jié)構(gòu)體則是 redisServer,存儲(chǔ) save 參數(shù)的結(jié)構(gòu)體為 saveparam。
1 // Redis 服務(wù)器狀態(tài)信息結(jié)構(gòu)體
2 struct redisServer {
3 // ... ...
4
5 // 記錄多個(gè) save 配置參數(shù)
6 struct saveparam *saveparams;
7 // 修改次數(shù)計(jì)數(shù)器
8 long long dirty;
9 // 上次執(zhí)行保存的時(shí)間
10 time_t lastsave;
11
12 // ... ...
13 }
14 // Save 參數(shù)結(jié)構(gòu)體 saveparam
15 struct saveparam {
16 // 秒數(shù)
17 time_t seconds;
18 // 修改數(shù)
19 int changes;
20 }看到上面 redisServer 結(jié)構(gòu)體的屬性信息,你心里應(yīng)該有答案了吧?dirty 表示的是自從上次執(zhí)行 SAVE 或者 BGSAVE 命令完成之后對數(shù)據(jù)庫進(jìn)行修改的次數(shù);lastsave 表示的是上次成功執(zhí)行SAVE 或者 BGSAVE 命令的時(shí)間。這個(gè)時(shí)候,如果再有個(gè)機(jī)制能夠定時(shí)檢查是否有滿足條件的配置參數(shù)就可以了。
Redis 提供了一個(gè)周期性操作函數(shù) serverCron,每 100 ms 會(huì)執(zhí)行一次。它其中的一項(xiàng)工作就是來檢查是否有符合條件的 save 參數(shù),如果存在符合條件的參數(shù)則執(zhí)行 BGSAVE 命令,執(zhí)行完畢之后將 dirty 和 lastsave 的值重置。相信只要有基礎(chǔ)的編程知識,根據(jù)這些變量就能實(shí)現(xiàn)這個(gè)檢查的過程吧。
1.3文件結(jié)構(gòu)

在上圖中,大寫字母的單詞表示的常量,小寫字母單詞則是變量和數(shù)據(jù)。RDB 文件開頭的“REDIS”是我們習(xí)慣稱為的魔數(shù),類似于 class 文件的 COFFEE,用來識別文件類型;緊接著長度為四個(gè)字節(jié)的 db_version 記錄的是 RDB 文件的版本號;database 表示的是所存儲(chǔ)的數(shù)據(jù);EOF 則表明數(shù)據(jù)內(nèi)容結(jié)束了;check_sum 的值是整個(gè)文件的校驗(yàn)和,用來檢查文件是否損壞。
2、AOF
其實(shí)持久化數(shù)據(jù)除了 RDB 這種方式,肯定會(huì)有同學(xué)能想到另一種方式,就是把服務(wù)端執(zhí)行的所有客戶端請求增加、修改和刪除等會(huì)改變數(shù)據(jù)的命令全都存儲(chǔ)起來。通過存儲(chǔ)這些命令數(shù)據(jù),在遇到機(jī)器宕機(jī)和服務(wù)進(jìn)程異常中斷的情況下重啟服務(wù)時(shí)只要執(zhí)行一遍這些持久化的命令即可恢復(fù)之前的數(shù)據(jù)了。(也是一個(gè)相當(dāng)好的辦法呀!)
原理就是如此,那么問題來了,假如同樣讓你來實(shí)現(xiàn)這個(gè)過程,你會(huì)考慮到哪些問題呢?
一是性能問題,執(zhí)行完命令之后是否直接將此命令持久化到磁盤上還是由操作系統(tǒng)控制文件同步?在這個(gè)問題上如何做取舍?二是文件大小問題,隨著 Redis 服務(wù)運(yùn)行越來越久,數(shù)據(jù)文件勢必會(huì)越來越大?應(yīng)該使用什么辦法解決?… …
我們來看下 Redis 的 AOF 的過程吧!
2.1持久化過程
首先,通過在配置文件中增加一行配置 appendonly yes 來開啟 AOF 持久化。
像 RDB 機(jī)制所依賴 redisServer 結(jié)構(gòu)體中的 saveparams、dirty、lastsave 參數(shù)一樣,AOF 的實(shí)現(xiàn)依賴 redisServer 結(jié)構(gòu)體中的 aof_buf 參數(shù)。
1 struct redisServer{
2 // ... ...
3
4 // AOF 緩沖區(qū)
5 sds aof_buf;
6
7 // ... ...
8 }aof_buf 參數(shù)用來以協(xié)議格式緩存會(huì)對數(shù)據(jù)進(jìn)行變更的命令。
在 Redis 服務(wù)器執(zhí)行完命令,并將命令以協(xié)議的格式追加到 aof_buf 緩沖區(qū)之后,在當(dāng)前這個(gè)事件循環(huán)結(jié)束之前,Redis 還會(huì)調(diào)用一個(gè)函數(shù) flushAppendOnlyFile,這個(gè)函數(shù)會(huì)根據(jù)配置文件中 appendfsync 的值來決定接下來的持久化行為。appendfsync 有三個(gè)可選值,分別是 always、everysec、no。
always: 將 aof_buf 緩沖區(qū)中的內(nèi)容寫入并同步到 AOF 文件。(性能最低,安全最高)
everysec: 將 aof_buf 緩沖區(qū)中的內(nèi)容寫入到 AOF 文件,如果上次同步 AOF 文件的時(shí)間距離現(xiàn)在超過一秒鐘,那么再次對 AOF 文件同步,并且這個(gè)同步是由一個(gè)線程專門負(fù)責(zé)的。(同時(shí)兼顧性能與安全,推薦)
no: 將 aof_buf 緩沖區(qū)中的內(nèi)容寫入到 AOF 文件,但并不負(fù)責(zé)對 AOF 文件的同步,把同步的控制權(quán)交由操作系統(tǒng)控制。(性能最高,安全最低)
以上就是 AOF 持久化的基本過程。
2.2數(shù)據(jù)載入
由于命令數(shù)據(jù)是以協(xié)議格式存儲(chǔ)至文件中的,所以在啟動(dòng) Redis 服務(wù)時(shí)檢測到 AOF 文件的存在后會(huì)啟動(dòng)載入程序。(如果 RDB 和 AOF 持久化的文件同時(shí)存在則會(huì)優(yōu)先載入 AOF 文件數(shù)據(jù))
啟動(dòng)載入程序后,其載入過程如下圖所示:

2.3AOF 重寫
在前面,我們提到 AOF 的這種機(jī)制會(huì)造成 AOF 數(shù)據(jù)文件越來越大,并且可能會(huì)存在許多無意義的命令。例如,先執(zhí)行了一個(gè)命令 set chang xuan ,隨后又執(zhí)行了命令 del chang 。其實(shí)這兩條語句都會(huì)被持久化到 AOF 文件中,但實(shí)際上除了能證明曾經(jīng)執(zhí)行過這兩條命令之外對于我們要持久化數(shù)據(jù)的目的而言并沒有什么作用。
對此,Redis 提供了 AOF 重寫的機(jī)制。
Redis 的 AOF 重寫其實(shí)是根據(jù)當(dāng)前存儲(chǔ)的數(shù)據(jù),生成命令的過程。并且會(huì)采用一些策略盡量減小 AOF 文件的大小,例如對于 List 中的數(shù)據(jù)會(huì)盡量使用較少的命令操作較多的數(shù)據(jù)。當(dāng)然,如果在當(dāng)前進(jìn)程中進(jìn)行重寫處理并且數(shù)據(jù)量特別大的情況下肯定會(huì)阻塞客戶端的請求,所以和 RDB 一樣,Redis 提供了 AOF 后臺(tái)重寫的機(jī)制。
后臺(tái)重寫(BGREWRITEAOF)
AOF 通過 fork 子進(jìn)程的方式進(jìn)行后臺(tái)重寫有兩個(gè)優(yōu)點(diǎn):
重寫期間服務(wù)器進(jìn)程可以繼續(xù)處理請求。
子進(jìn)程帶有服務(wù)器進(jìn)程的數(shù)據(jù)副本,能充分利用操作系統(tǒng)提供的寫時(shí)復(fù)制機(jī)制從而提升效率,還可以在避免使用鎖的情況下保證數(shù)據(jù)的安全性。
天下沒有免費(fèi)的午餐,這種方式還帶來一個(gè)問題。就是在使用子進(jìn)程重寫期間,如果父進(jìn)程還在處理著客戶端請求,如何保證重寫后 AOF 文件數(shù)據(jù)的一致性呢?
對于這個(gè)問題,Redis 設(shè)置了一個(gè) AOF 重寫緩沖區(qū)。在子進(jìn)程被創(chuàng)建后,Redis 服務(wù)器就會(huì)啟用這個(gè)重寫緩沖區(qū)。在將命令以協(xié)議格式追加到 AOF 緩沖區(qū)之后,同時(shí)也會(huì)追加到 AOF 重寫緩沖區(qū)。
當(dāng)子進(jìn)程完成重寫工作后會(huì)向父進(jìn)程發(fā)送一個(gè)信號。父進(jìn)程接收到信后之后會(huì)進(jìn)行調(diào)用相關(guān)函數(shù),進(jìn)行以下工作:
將 AOF 重寫緩沖區(qū)中的內(nèi)容寫入到新的 AOF 文件中。
對新的 AOF 文件進(jìn)行改名,原子地覆蓋現(xiàn)有的 AOF 文件,完成新舊文件的替換。
這時(shí),就完成了一次 AOF 后臺(tái)重寫。
3、總結(jié)
通過前文內(nèi)容,我們可以大致清楚 Redis 所提供的 RDB 和 AOF 兩種持久化機(jī)制的過程以及基本原理。它們各有特點(diǎn),也各有適合使用的場景所以并不能說誰一定比誰好。通過搭配使用,能夠確保線上環(huán)境數(shù)據(jù)的安全性就是最好的。
文章來源:https://urlify.cn/faauM3

到此文章就結(jié)束了。如果今天的文章對你在進(jìn)階架構(gòu)師的路上有新的啟發(fā)和進(jìn)步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進(jìn)階架構(gòu)師,在后臺(tái)回復(fù)“加群”即可入群。
這些年小編給你分享過的干貨
1.SpringBoot物流管理項(xiàng)目,拿去學(xué)習(xí)吧(附源碼)
2.ERP系統(tǒng),自帶進(jìn)銷存+財(cái)務(wù)+生產(chǎn)功能,拿來即用(附源碼)
3.帶工作流的SpringBoot后臺(tái)管理項(xiàng)目快速開發(fā)(附源碼)
4.最好的OA系統(tǒng),拿來即用,非常方便(附源碼)
5.SpringBoot+Vue完整的外賣系統(tǒng),手機(jī)端和后臺(tái)管理,附源碼!
6.SpringBoot+Vue 可視化拖拽編輯的大屏項(xiàng)目(附源碼)

轉(zhuǎn)發(fā)在看就是最大的支持??
