Redis將數(shù)據(jù)存儲(chǔ)在內(nèi)存中,宕機(jī)或重啟都會(huì)使內(nèi)存數(shù)據(jù)全部丟失, Redis的持久化機(jī)制用來保證數(shù)據(jù)不會(huì)因?yàn)楣收隙鴣G失。
Redis提供兩種持久化方式,一種為內(nèi)存快照方式,生成rdb文件
,rdb是某一時(shí)間點(diǎn)內(nèi)存數(shù)據(jù)的全量備份,文件內(nèi)容是存儲(chǔ)結(jié)構(gòu)非常緊湊的二進(jìn)制序列化形式;另一種是AOF日志
備份方式,日志保存的是基于數(shù)據(jù)的連續(xù)增量備份,日志文件內(nèi)容是服務(wù)端執(zhí)行的每一條指令的記錄文本。
兩種方式各有優(yōu)略,下面的章節(jié)會(huì)詳細(xì)介紹兩種持久化機(jī)制的實(shí)現(xiàn)原理和使用技巧。
1.內(nèi)存快照
Redis進(jìn)行快照數(shù)據(jù)持久化時(shí),為了不阻塞線上業(yè)務(wù),要能夠響應(yīng)客戶端請(qǐng)求??煺粘志没墓ぷ魇菍⒁粋€(gè)時(shí)間點(diǎn)內(nèi)存數(shù)據(jù)序列化后同步到磁盤rdb文件。備份數(shù)據(jù)如何在內(nèi)存中瞬間凝固,不再改變?文件IO操做怎樣才能不拖累服務(wù)端對(duì)客戶端的正常響應(yīng)?這一切都要從Copy On Write
說起。
1.1 快照原理——Copy On Write
Copy On Write簡(jiǎn)寫為COW
,又叫寫時(shí)復(fù)制
,是操作系統(tǒng)為優(yōu)化使用子進(jìn)程采取的一種策略。
類Unix系統(tǒng)創(chuàng)建進(jìn)程的主要方式是調(diào)用glibc
的函數(shù)fork
,熟悉Linux的人都知道:Linux操作系統(tǒng)的進(jìn)程都是通過init
進(jìn)程(pid=1)或者其子進(jìn)程fork(vfork)
出來的。
fork()
會(huì)產(chǎn)生一個(gè)與父進(jìn)程完全相同的子進(jìn)程,有兩次返回:將子進(jìn)程的pid
返回給父進(jìn)程,0返回給子進(jìn)程。如果小于0,說明創(chuàng)建進(jìn)程失?。∠旅媸且粋€(gè)C語言的例子:
#include
#include
intmain(){
pid_tpid;
intcount=0;
pid=fork();
if(pid0)
printf("errorinfork!");
elseif(pid==0){
printf("childprocess,processidis%d/n",getpid());
count++;
}else{
printf("parentprocess,processidis%d/n",getpid());
count++;
}
printf("counttotal:%d/n",count);
return0;
}
輸出結(jié)果為:
parentprocess,processidis23049
counttotal:1
childprocess,processidis23050
counttotal:1
當(dāng)前進(jìn)程調(diào)用fork(),會(huì)創(chuàng)建一個(gè)跟當(dāng)前進(jìn)程完全相同的子進(jìn)程(除了pid),所以子進(jìn)程同樣是會(huì)執(zhí)行fork()之后的代碼。父子進(jìn)程的count變量都是1,說明父子進(jìn)程使用了各自獨(dú)有的棧區(qū)(count變量存放在棧區(qū))。
我們先來看一下CPU執(zhí)行程序的流程。

CPU在加載執(zhí)行程序時(shí),首先按照虛擬地址來尋址,然后通過MMU
(內(nèi)存管理單元)將虛擬地址轉(zhuǎn)換為物理地址。因?yàn)橹挥谐绦虻囊徊糠旨尤氲絻?nèi)存中(按頁加載),所以會(huì)出現(xiàn)所尋找的地址不在內(nèi)存中的情況(CPU產(chǎn)生缺頁異常),如果在內(nèi)存不足的情況下,就會(huì)通過頁面調(diào)度算法來將內(nèi)存中的頁面置換出來,然后將在外存中的頁面加入到內(nèi)存中,使程序繼續(xù)正常運(yùn)行。
Linux操作系統(tǒng)的每一個(gè)進(jìn)程,都會(huì)分配有虛擬地址
和物理地址
,虛擬地址和物理地址通過MMU
保持映射關(guān)系。一個(gè)進(jìn)程是一個(gè)主體,它有靈魂有身體,靈魂就是其虛擬地址空間(有相應(yīng)的數(shù)據(jù)結(jié)構(gòu)表示),包括:正文段、數(shù)據(jù)段、堆、棧這四個(gè)部分;相應(yīng)的,內(nèi)核會(huì)為這四個(gè)部分分配各自的物理塊(進(jìn)程的身體)即:正文段塊、數(shù)據(jù)段塊、堆塊、棧塊。
我們?cè)賮砜匆幌?code style="font-size:14px;padding:2px 4px;margin:0 2px;color:#1e6bb8;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">fork進(jìn)程時(shí)寫時(shí)復(fù)制
的過程:

在 fork
產(chǎn)生子進(jìn)程時(shí),操作系統(tǒng)只為新生成的子進(jìn)程創(chuàng)建虛擬空間結(jié)構(gòu),它們復(fù)制于父進(jìn)程的虛擬空間結(jié)構(gòu),但是不為這些段分配物理內(nèi)存,它們共享父進(jìn)程的物理空間,當(dāng)父子進(jìn)程中有更改相應(yīng)段的行為發(fā)生時(shí),再為子進(jìn)程相應(yīng)的段分配物理空間,這就是寫時(shí)復(fù)制。
1.2 快照?qǐng)?zhí)行流程
Redis在持久化時(shí)會(huì)調(diào)用glibc
的函數(shù)fork
產(chǎn)生一個(gè)子進(jìn)程,快照持久化完全交給子進(jìn)程來處理,父進(jìn)程繼續(xù)處理客戶端請(qǐng)求。

可以通過在Redis客戶端輸入bgsave
命令來觸發(fā)快照保存操作,Redis調(diào)用bgsaveCommand
函數(shù),該函數(shù)fork一個(gè)子進(jìn)程,子進(jìn)程剛剛產(chǎn)生時(shí),它和父進(jìn)程共享內(nèi)存里面的代碼段和數(shù)據(jù)段。這時(shí)將父子進(jìn)程比喻成一個(gè)連體嬰兒 非常恰當(dāng),這是Linux操作系統(tǒng)的機(jī)制,為了節(jié)約內(nèi)存資源,盡可能的將內(nèi)存資源共享起來。在進(jìn)程分離的一瞬間,內(nèi)存的增長(zhǎng)幾乎沒有明顯的變化。子進(jìn)程因?yàn)闆]有數(shù)據(jù)的變化,它能感知到的內(nèi)存數(shù)據(jù)在進(jìn)程產(chǎn)生的一瞬間就凝固了,再也不會(huì)改變。父進(jìn)程可以繼續(xù)處理客戶端請(qǐng)求,當(dāng)子進(jìn)程推出后,父進(jìn)程調(diào)用相關(guān)函數(shù)處理子進(jìn)程的善后工作。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
2.AOF持久化
AOF日志存儲(chǔ)的Redis服務(wù)器的順序指令序列,只記錄對(duì)內(nèi)存進(jìn)行修改的指令記錄。有了AOF文件,就可以通過一個(gè)空的Redis實(shí)例順序執(zhí)行所有的指令來恢復(fù)Redis當(dāng)前實(shí)例的內(nèi)存數(shù)據(jù)結(jié)構(gòu)的狀態(tài),這個(gè)過程叫做重放
。
2.1 AOF日志文件寫入
AOF日志以文件的形式存在,寫文件通過操作系統(tǒng)提供的write
函數(shù)執(zhí)行,但是write之后的數(shù)據(jù)只是寫到了內(nèi)核的一個(gè)緩沖區(qū)中,然后內(nèi)核還需要異步的調(diào)用fsync
函數(shù)異步的將數(shù)據(jù)刷回磁盤。fsync函數(shù)是一個(gè)阻塞并且緩慢的操作,如果機(jī)器突然宕機(jī),AOF日志內(nèi)容可能還沒來的及完全刷到磁盤,這時(shí)候就會(huì)丟失數(shù)據(jù)。Redis通過appendfsync
配置控制執(zhí)行fsync的頻次,具體有如下三種模式:
- no: 永遠(yuǎn)不調(diào)用fsync,讓操作系統(tǒng)決定何時(shí)同步磁盤,這樣做很不安全,但是Redis的性能最高。
- always: 每執(zhí)行一次寫入操作就執(zhí)行一次fsync,雖然數(shù)據(jù)安全性高,會(huì)導(dǎo)致執(zhí)行非常緩慢。
- everysec: 每隔1s執(zhí)行一次fsync,這個(gè)1s的周期是可以配置的,這是數(shù)據(jù)安全性和性能之間的折中方案,在保證高性能的同時(shí),盡量使數(shù)據(jù)少丟失。推薦在生產(chǎn)環(huán)境中使用。
2.2 AOF執(zhí)行流程
Redis收到客戶端的指令以后,首先進(jìn)行參數(shù)校驗(yàn)、邏輯處理,如果沒有問題,會(huì)判斷是否開啟AOF,如果開啟,則會(huì)將每條命令執(zhí)行完畢后同步寫入aof_buf
中,aof_buf是個(gè)全局的SDS類型的緩沖區(qū)。

每一條命令的執(zhí)行都會(huì)調(diào)用call函數(shù),注意:Redis服務(wù)端是先執(zhí)行指令再將命令寫入aof_buf。
2.3 日志瘦身——AOF重寫
Redis服務(wù)端在長(zhǎng)期運(yùn)行過程中,AOF日志會(huì)越來越長(zhǎng),如果實(shí)例宕機(jī)或者重啟,重放整個(gè)AOF日志會(huì)非常耗時(shí),導(dǎo)致Redis長(zhǎng)時(shí)間無法對(duì)外提供服務(wù),所以需要對(duì)AOF日志進(jìn)行瘦身,即:**AOF重寫
。**
我們考慮一下AOF和RDB文件的加載過程:RDB只需要把相應(yīng)的數(shù)據(jù)加載都內(nèi)存并生成相應(yīng)的數(shù)據(jù)結(jié)構(gòu)就可以了,有些結(jié)構(gòu)如intset、ziplist,保存的時(shí)候直接按照字符串保存,加載速度非???。但是AOF日志文件的加載需要?jiǎng)?chuàng)建一個(gè)偽客戶端,順序執(zhí)行一遍命令,根據(jù)Redis作者做的測(cè)試,RDB在10~20秒能加載1GB的文件,AOF的速度是RDB的一半(如果做了AOF重寫會(huì)加快)
通過Redis客戶端bgrewriteaof
指令對(duì)AOF日志進(jìn)行瘦身過程如下:

Redis服務(wù)端調(diào)用bgrewriteaofCommand
命令創(chuàng)建管道,創(chuàng)建管道對(duì)作用是AOF重寫過程中批量接收服務(wù)端累積的命令;創(chuàng)建完管道以后,fork進(jìn)程,子進(jìn)程調(diào)用rewriteAppendOnlyFile
執(zhí)行AOF重寫操作;父進(jìn)程記錄一些統(tǒng)計(jì)指標(biāo)后繼續(xù)進(jìn)入主循環(huán)處理客戶端請(qǐng)求,待子進(jìn)程結(jié)束以后,處理一些善后工作。瘦身工作就是子進(jìn)程對(duì)所有數(shù)據(jù)庫(kù)中的鍵各自生成一條相應(yīng)的執(zhí)行命令,最后將重寫開始后父進(jìn)程繼續(xù)執(zhí)行的命令進(jìn)行回放,生成一個(gè)新的AOF文件。
例如執(zhí)行了下面的命令:
127.0.0.1:6379>lpushlistguozhaoran
(integer)3
127.0.0.1:6379>lpoplist
"ran"
127.0.0.1:6379>lpoplist
"zhao"
127.0.0.1:6379>lpushlistzhao
(integer)2
AOF文件會(huì)保存對(duì)list操作的4條命令,但是list現(xiàn)在內(nèi)存中的元素是這樣的:
127.0.0.1:6379>lrangelist0-1
1)"zhao"
2)"guo"
AOF重寫以后就日志文件內(nèi)容直接就變?yōu)榱?code style="font-size:14px;padding:2px 4px;margin:0 2px;color:#1e6bb8;background-color:rgba(27,31,35,.05);font-family:'Operator Mono', Consolas, Monaco, Menlo, monospace;">lpush list zhao guo。日志瘦身既可以減小文件大小,又可以提高加載速度。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
3.混合持久化
RDB和AOF實(shí)現(xiàn)持久化的方式各有優(yōu)缺點(diǎn),我們來簡(jiǎn)單總結(jié)一下:
RDB 保存的是一個(gè)時(shí)間的快照,如果發(fā)生故障,丟失的是最后一次RDB執(zhí)行時(shí)間點(diǎn)到故障發(fā)生的時(shí)間間隔之內(nèi)產(chǎn)生的數(shù)據(jù)。如果Redis數(shù)據(jù)量很大,QPS很高,執(zhí)行一次RDB需要的時(shí)間會(huì)相應(yīng)增加,發(fā)生故障時(shí)丟失的數(shù)據(jù)也會(huì)增多。
AOF 保存的是一條條的命令,理論上可以做到發(fā)生故障時(shí)只丟失一條命令。但是由于操作系統(tǒng)中執(zhí)行寫文件操作代價(jià)很大,Redis提供了配置參數(shù),可以對(duì)完全性和性能取折中,設(shè)置不同的配置策略。但是重放AOF日志相對(duì)于使用RDB來說還是慢很多。
Redis4.0為了解決這個(gè)問題,帶來了一個(gè)新的持久化選項(xiàng)——混合持久化?;旌铣志没侵高M(jìn)行AOF重寫時(shí)子進(jìn)程將當(dāng)前時(shí)間點(diǎn)的數(shù)據(jù)快照保存為RDB文件格式,而后將父進(jìn)程累積命令保存為AOF格式,最終生成的格式如下圖所示:

將RDB文件內(nèi)容和增量AOF日志文件存在一起,這里的AOF日志不再是全量日志,通常這部分AOF日志很小。于是在Redis重啟的時(shí)候,可以先加載rdb內(nèi)容,然后再重放增量AOF日志,就可以完全替代之前的AOF全量文件重放,重啟效率會(huì)得到大幅度提升。
4.Redis持久化相關(guān)配置
下面總結(jié)一下Redis4.0版持久化相關(guān)的配置及其含義。
配置項(xiàng) | 可選值 | 默認(rèn)值 | 作用 |
---|---|---|---|
save | save | save 900 1 save 300 10 save 60 10000 | save "":禁用快照備份,默認(rèn)關(guān)閉 save 900 1:在900秒內(nèi)有1個(gè)key被改動(dòng),自動(dòng)保存到dump.rdb文件中 save 300 10:在300秒內(nèi)有10個(gè)key被改動(dòng),自動(dòng)保存到dump.rdb文件中 save 60 10000:在60秒內(nèi)有10000個(gè)key被改動(dòng),自動(dòng)保存到dump.rdb文件中 以上3中條件任意一種被滿足就會(huì)觸發(fā)保存 |
stop-writes-on-bgsave-error | yes/no | yes | 開啟該參數(shù)后,如果開啟了RDB快照(即配置了save指令),并且最近一次快照?qǐng)?zhí)行失敗,則Redis將停止接收寫相關(guān)的請(qǐng)求 |
rdbcompression | yes/no | yes | 執(zhí)行rdb的時(shí)候是否將string類型的數(shù)據(jù)壓縮 |
rdbchecksum | yes/no | yes | 是否開啟rdb文件內(nèi)容的校驗(yàn) |
dbfilename | 文件名稱 | dump.rdb | rdb文件名稱 |
dir | 文件路徑 | ./ | RDB和AOF文件存放路徑 |
appendonly | yes/no | no | 是否開啟AOF功能 |
appendfilename | 文件名稱 | appendonly.aof | AOF文件名稱 |
appendfsync | always/everysec/no | everysec | fsync執(zhí)行頻次,上邊有說到 |
no-appendfsync-on-rewrite | yes/no | no | 開啟該參數(shù)后,如果后臺(tái)正在執(zhí)行一次rdb快照或者aof重寫,則主進(jìn)程不再進(jìn)行fsync操作,即使將appendsync配置成always或者everysec |
auto-aof-rewrite-percentage | 百分比 | 100 | 和auto-aof-rewrite-min-size配和使用,下面會(huì)講解 |
auto-aof-rewrite-min-size | 文件大小 | 64M | 當(dāng)AOF文件大于64M時(shí),并且AOF文件當(dāng)前大小比基準(zhǔn)大小增長(zhǎng)了100%時(shí)會(huì)觸發(fā)一次AOF重寫。 |
aof-load-truncated | yes/no | yes | AOF以追加日志的方式生成,當(dāng)服務(wù)端發(fā)生故障時(shí)會(huì)有命令不完整的情況。開啟該參數(shù)后,在這種情況下,AOF會(huì)截?cái)辔膊坎煌暾拿罾^續(xù)加載,并且在日志中給出提示。 |
aof-use-rdb-preamble | yes/no | yes | 是否開啟混合持久化 |
aof-rewrite-incremental-fsync | yes/no | yes | 開啟該參數(shù)后,AOF重寫時(shí)每產(chǎn)生32MB數(shù)據(jù)執(zhí)行一次fsync |
5.總結(jié)
Redis是內(nèi)存數(shù)據(jù)庫(kù),機(jī)器故障或重啟之后,內(nèi)存數(shù)據(jù)全部丟失,所以需要持久化來保證數(shù)據(jù)安全。
Redis提供了快照RDB和AOF日志同步兩種方式進(jìn)行數(shù)據(jù)持久化,快照RDB實(shí)現(xiàn)原理是Copy On Write,優(yōu)點(diǎn)是機(jī)器加載速度快,缺點(diǎn)是執(zhí)行緩慢,QPS高的情況下會(huì)丟失大量數(shù)據(jù);AOF則是將命令一條條的有序存放到日志文件中,優(yōu)點(diǎn)是盡可能少的丟失數(shù)據(jù),缺點(diǎn)是日志文件重放緩慢,日志文件會(huì)很大,可以通過重寫AOF日志來實(shí)現(xiàn),另外提供了這種的配置方案異步執(zhí)行fsync操作。
生產(chǎn)環(huán)境中推薦使用混合持久化,這種方式綜合了RDB和AOF兩種方式的優(yōu)點(diǎn)。文章最后總結(jié)了一下Redis持久化配置項(xiàng)。本文是筆者學(xué)習(xí)Redis持久化的總結(jié),希望能對(duì)讀者有所幫助。
審核編輯:湯梓紅
-
cpu
+關(guān)注
關(guān)注
68文章
11076瀏覽量
216998 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3122瀏覽量
75243 -
Redis
+關(guān)注
關(guān)注
0文章
386瀏覽量
11437
原文標(biāo)題:深度剖析Redis持久化機(jī)制
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
如何使得redis中的數(shù)據(jù)不再有
談?wù)?b class='flag-5'>Redis怎樣配置實(shí)現(xiàn)主從復(fù)制?
Redis持久化分為兩種:RDB和AOF
redis持久化方式有幾種及配置
redis兩種持久化方式的區(qū)別
redis的持久化方式RDB和AOF的區(qū)別
redis持久化機(jī)制和如何實(shí)現(xiàn)持久化
redis持久化機(jī)制優(yōu)缺點(diǎn)
redis里數(shù)據(jù)什么時(shí)候持久化
云容器redis持久化配置
redis持久化rdb和aof一起用好處
Redis使用重要的兩個(gè)機(jī)制:Reids持久化和主從復(fù)制

評(píng)論