詳解Redis的持久化機(jī)制--RDB和AOF
redis跟memcached類(lèi)似,都是內(nèi)存數(shù)據(jù)庫(kù),不過(guò)redis支持?jǐn)?shù)據(jù)持久化,也就是說(shuō)redis可以將內(nèi)存中的數(shù)據(jù)同步到磁盤(pán)來(lái)持久化,以確保redis 的數(shù)據(jù)安全。不過(guò)持久化這塊可能比較容易產(chǎn)生誤解,下面聊聊這塊。
Redis持久化是如何工作的?
什么是持久化?簡(jiǎn)單來(lái)講就是將數(shù)據(jù)放到斷電后數(shù)據(jù)不會(huì)丟失的設(shè)備中,也就是我們通常理解的硬盤(pán)上。
1. 數(shù)據(jù)庫(kù)寫(xiě)操作的5個(gè)過(guò)程
首先我們來(lái)看一下數(shù)據(jù)庫(kù)在進(jìn)行寫(xiě)操作時(shí)到底做了哪些事,主要有下面五個(gè)過(guò)程:
- 客戶(hù)端向服務(wù)端發(fā)送寫(xiě)操作(數(shù)據(jù)在客戶(hù)端的內(nèi)存中)。
- 數(shù)據(jù)庫(kù)服務(wù)端接收到寫(xiě)請(qǐng)求的數(shù)據(jù)(數(shù)據(jù)在服務(wù)端的內(nèi)存中)。
- 服務(wù)端調(diào)用write這個(gè)系統(tǒng)調(diào)用,將數(shù)據(jù)往磁盤(pán)上寫(xiě)(數(shù)據(jù)在系統(tǒng)內(nèi)存的緩沖區(qū)中)。
- 操作系統(tǒng)將緩沖區(qū)中的數(shù)據(jù)轉(zhuǎn)移到磁盤(pán)控制器上(數(shù)據(jù)在磁盤(pán)緩存中)。
- 磁盤(pán)控制器將數(shù)據(jù)寫(xiě)到磁盤(pán)的物理介質(zhì)中(數(shù)據(jù)真正落到磁盤(pán)上)。
2. 故障分析
寫(xiě)操作大致有上面5個(gè)流程,當(dāng)數(shù)據(jù)庫(kù)系統(tǒng)故障時(shí),這時(shí)候系統(tǒng)內(nèi)核還是完好的。那么此時(shí)只要我們執(zhí)行完了第3步,那么數(shù)據(jù)就是安全的,因?yàn)楹罄m(xù)操作系統(tǒng)會(huì)來(lái)完成后面幾步,保證數(shù)據(jù)最終會(huì)落到磁盤(pán)上。當(dāng)系統(tǒng)斷電時(shí),這時(shí)候上面5項(xiàng)中提到的所有緩存都會(huì)失效,并且數(shù)據(jù)庫(kù)和操作系統(tǒng)都會(huì)停止工作。所以只有當(dāng)數(shù)據(jù)在完成第5步后,才能保證在斷電后數(shù)據(jù)不丟失。
【補(bǔ)充】這里可能有幾個(gè)疑問(wèn):
- 數(shù)據(jù)庫(kù)多長(zhǎng)時(shí)間調(diào)用一次write,將數(shù)據(jù)寫(xiě)到內(nèi)核緩沖區(qū)?
- 內(nèi)核多長(zhǎng)時(shí)間會(huì)將系統(tǒng)緩沖區(qū)中的數(shù)據(jù)寫(xiě)到磁盤(pán)控制器?
- 磁盤(pán)控制器又在什么時(shí)候把緩存中的數(shù)據(jù)寫(xiě)到物理介質(zhì)上?
對(duì)于***個(gè)問(wèn)題,通常數(shù)據(jù)庫(kù)層面會(huì)進(jìn)行全面控制。
而對(duì)第二個(gè)問(wèn)題,操作系統(tǒng)有其默認(rèn)的策略,但是我們也可以通過(guò)POSIX API提供的fsync系列命令強(qiáng)制操作系統(tǒng)將數(shù)據(jù)從內(nèi)核區(qū)寫(xiě)到磁盤(pán)控制器上。
對(duì)于第三個(gè)問(wèn)題,看起來(lái)數(shù)據(jù)庫(kù)已經(jīng)無(wú)法觸及,但實(shí)際上,大多數(shù)情況下磁盤(pán)緩存是被設(shè)置關(guān)閉的,或者是只開(kāi)啟為讀緩存,也就是說(shuō)寫(xiě)操作不會(huì)進(jìn)行緩存,直接寫(xiě)到磁盤(pán)。建議的做法是僅僅當(dāng)你的磁盤(pán)設(shè)備有備用電池時(shí)才開(kāi)啟寫(xiě)緩存。
3. 數(shù)據(jù)損壞
所謂數(shù)據(jù)損壞,就是數(shù)據(jù)無(wú)法恢復(fù),上面我們講的都是如何保證數(shù)據(jù)是確實(shí)寫(xiě)到磁盤(pán)上去,但是寫(xiě)到磁盤(pán)上可能并不意味著數(shù)據(jù)不會(huì)損壞。比如我們可能一次寫(xiě)請(qǐng)求會(huì)進(jìn)行兩次不同的寫(xiě)操作,當(dāng)意外發(fā)生時(shí),可能會(huì)導(dǎo)致一次寫(xiě)操作安全完成,但是另一次還沒(méi)有進(jìn)行。如果數(shù)據(jù)庫(kù)的數(shù)據(jù)文件結(jié)構(gòu)組織不合理,可能就會(huì)導(dǎo)致數(shù)據(jù)完全不能恢復(fù)的狀況出現(xiàn)。
三種解決策略:
- 最粗糙的處理,就是不通過(guò)數(shù)據(jù)的組織形式保證數(shù)據(jù)的可恢復(fù)性。而是通過(guò)配置數(shù)據(jù)同步備份的方式,在數(shù)據(jù)文件損壞后通過(guò)數(shù)據(jù)備份來(lái)進(jìn)行恢復(fù)。實(shí)際上MongoDB在不開(kāi)啟操作日志,通過(guò)配置Replica Sets時(shí)就是這種情況。
- 在上面基礎(chǔ)上添加一個(gè)操作日志,每次操作時(shí)記一下操作的行為,這樣我們可以通過(guò)操作日志來(lái)進(jìn)行數(shù)據(jù)恢復(fù)。因?yàn)椴僮魅罩臼琼樞蜃芳拥姆绞綄?xiě)的,所以不會(huì)出現(xiàn)操作日志也無(wú)法恢復(fù)的情況。這也類(lèi)似于MongoDB開(kāi)啟了操作日志的情況。
- 更保險(xiǎn)的做法是數(shù)據(jù)庫(kù)不進(jìn)行舊數(shù)據(jù)的修改,只是以追加方式去完成寫(xiě)操作,這樣數(shù)據(jù)本身就是一份日志,這樣就永遠(yuǎn)不會(huì)出現(xiàn)數(shù)據(jù)無(wú)法恢復(fù)的情況了。實(shí)際上CouchDB就是此做法的優(yōu)秀范例。
那么,redis又針對(duì)持久化提供了什么方式呢?
redis持久化的兩種方式
redis提供了兩種持久化的方式,分別是RDB(Redis DataBase)和AOF(Append Only File)。
RDB,簡(jiǎn)而言之,就是將存儲(chǔ)的數(shù)據(jù)快照的方式存儲(chǔ)到磁盤(pán)上,
AOF,則是將redis執(zhí)行過(guò)的所有寫(xiě)指令記錄下來(lái),通過(guò)write函數(shù)追加到AOF文件的末尾。在下次redis重新啟動(dòng)時(shí),只要把這些寫(xiě)指令從前到后再重復(fù)執(zhí)行一遍,就可以實(shí)現(xiàn)數(shù)據(jù)恢復(fù)了。
RDB機(jī)制
1. 概念
RDB持久化是指在指定的時(shí)間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫(xiě)入磁盤(pán)。也是默認(rèn)的持久化方式,這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫(xiě)入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb。
可以通過(guò)配置設(shè)置自動(dòng)做快照持久化的方式。我們可以配置redis在n秒內(nèi)如果超過(guò)m個(gè)key被修改就自動(dòng)做快照,下面是默認(rèn)的快照保存配置
- save 900 1 #900秒內(nèi)如果超過(guò)1個(gè)key被修改,則發(fā)起快照保存
- save 300 10 #300秒內(nèi)容如超過(guò)10個(gè)key被修改,則發(fā)起快照保存
- save 60 10000
2. RDB文件保存過(guò)程
- redis調(diào)用fork,現(xiàn)在有了子進(jìn)程和父進(jìn)程。
- 父進(jìn)程繼續(xù)處理client請(qǐng)求,子進(jìn)程負(fù)責(zé)將內(nèi)存內(nèi)容寫(xiě)入到臨時(shí)文件。由于os的寫(xiě)時(shí)復(fù)制機(jī)制(copy on write)父子進(jìn)程會(huì)共享相同的物理頁(yè)面,當(dāng)父進(jìn)程處理寫(xiě)請(qǐng)求時(shí)os會(huì)為父進(jìn)程要修改的頁(yè)面創(chuàng)建副本,而不是寫(xiě)共享的頁(yè)面。所以子進(jìn)程的地址空間內(nèi)的數(shù) 據(jù)是fork時(shí)刻整個(gè)數(shù)據(jù)庫(kù)的一個(gè)快照。
- 當(dāng)子進(jìn)程將快照寫(xiě)入臨時(shí)文件完畢后,用臨時(shí)文件替換原來(lái)的快照文件,然后子進(jìn)程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線(xiàn)程中保存快照的,由于redis是用一個(gè)主線(xiàn)程來(lái)處理所有 client的請(qǐng)求,這種方式會(huì)阻塞所有client請(qǐng)求。所以不推薦使用。
另一點(diǎn)需要注意的是,每次快照持久化都是將內(nèi)存數(shù)據(jù)完整寫(xiě)入到磁盤(pán)一次,并不是增量的只同步臟數(shù)據(jù)。如果數(shù)據(jù)量大的話(huà),而且寫(xiě)操作比較多,必然會(huì)引起大量的磁盤(pán)io操作,可能會(huì)嚴(yán)重影響性能。
3. 優(yōu)勢(shì)
- 一旦采用該方式,那么你的整個(gè)Redis數(shù)據(jù)庫(kù)將只包含一個(gè)文件,這樣非常方便進(jìn)行備份。比如你可能打算每1天歸檔一些數(shù)據(jù)。
- 方便備份,我們可以很容易的將一個(gè)一個(gè)RDB文件移動(dòng)到其他的存儲(chǔ)介質(zhì)上
- RDB 在恢復(fù)大數(shù)據(jù)集時(shí)的速度比 AOF 的恢復(fù)速度要快。
- RDB 可以***化 Redis 的性能:父進(jìn)程在保存 RDB 文件時(shí)唯一要做的就是 fork 出一個(gè)子進(jìn)程,然后這個(gè)子進(jìn)程就會(huì)處理接下來(lái)的所有保存工作,父進(jìn)程無(wú)須執(zhí)行任何磁盤(pán) I/O 操作。
4. 劣勢(shì)
- 如果你需要盡量避免在服務(wù)器故障時(shí)丟失數(shù)據(jù),那么 RDB 不適合你。 雖然 Redis 允許你設(shè)置不同的保存點(diǎn)(save point)來(lái)控制保存 RDB 文件的頻率, 但是, 因?yàn)镽DB 文件需要保存整個(gè)數(shù)據(jù)集的狀態(tài), 所以它并不是一個(gè)輕松的操作。 因此可能會(huì)至少 5 分鐘才保存一次 RDB 文件。 在這種情況下, 一旦發(fā)生故障停機(jī)就可能會(huì)丟失好幾分鐘的數(shù)據(jù)。
- 每次保存 RDB 的時(shí)候,Redis 都要 fork() 出一個(gè)子進(jìn)程,并由子進(jìn)程來(lái)進(jìn)行實(shí)際的持久化工作。 在數(shù)據(jù)集比較龐大時(shí), fork() 可能會(huì)非常耗時(shí),造成服務(wù)器在某某毫秒內(nèi)停止處理客戶(hù)端; 如果數(shù)據(jù)集非常巨大,并且 CPU 時(shí)間非常緊張的話(huà),那么這種停止時(shí)間甚至可能會(huì)長(zhǎng)達(dá)整整一秒。 雖然 AOF 重寫(xiě)也需要進(jìn)行 fork() ,但無(wú)論 AOF 重寫(xiě)的執(zhí)行間隔有多長(zhǎng),數(shù)據(jù)的耐久性都不會(huì)有任何損失。
AOF
1. 概念
redis會(huì)將每一個(gè)收到的寫(xiě)命令都通過(guò)write函數(shù)追加到文件中(默認(rèn)是 appendonly.aof)。
當(dāng)redis重啟時(shí)會(huì)通過(guò)重新執(zhí)行文件中保存的寫(xiě)命令來(lái)在內(nèi)存中重建整個(gè)數(shù)據(jù)庫(kù)的內(nèi)容。當(dāng)然由于os會(huì)在內(nèi)核中緩存 write做的修改,所以可能不是立即寫(xiě)到磁盤(pán)上。這樣aof方式的持久化也還是有可能會(huì)丟失部分修改。
可以通過(guò)配置文件告訴redis通過(guò)fsync函數(shù)強(qiáng)制os寫(xiě)入到磁盤(pán)的時(shí)機(jī)。有三種方式如下(默認(rèn)是:每秒fsync一次)
- appendonly yes //啟用aof持久化方式
- # appendfsync always //每次收到寫(xiě)命令就立即強(qiáng)制寫(xiě)入磁盤(pán),最慢的,但是保證完全的持久化,不推薦使用
- appendfsync everysec //每秒鐘強(qiáng)制寫(xiě)入磁盤(pán)一次,在性能和持久化方面做了很好的折中,推薦
- # appendfsync no //完全依賴(lài)os,性能***,持久化沒(méi)保證
2. AOF文件保存過(guò)程
aof 的方式也同時(shí)帶來(lái)了另一個(gè)問(wèn)題。持久化文件會(huì)變的越來(lái)越大。例如我們調(diào)用incr test命令100次,文件中必須保存全部的100條命令,其實(shí)有99條都是多余的。因?yàn)橐謴?fù)數(shù)據(jù)庫(kù)的狀態(tài)其實(shí)文件中保存一條set test 100就夠了。
為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類(lèi)似的方式將內(nèi)存中的數(shù)據(jù) 以命令的方式保存到臨時(shí)文件中,***替換原來(lái)的文件。具體過(guò)程如下:
- redis調(diào)用fork ,現(xiàn)在有父子兩個(gè)進(jìn)程
- 子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫(kù)快照,往臨時(shí)文件中寫(xiě)入重建數(shù)據(jù)庫(kù)狀態(tài)的命令
- 父進(jìn)程繼續(xù)處理client請(qǐng)求,除了把寫(xiě)命令寫(xiě)入到原來(lái)的aof文件中。同時(shí)把收到的寫(xiě)命令緩存起來(lái)。這樣就能保證如果子進(jìn)程重寫(xiě)失敗的話(huà)并不會(huì)出問(wèn)題。
- 當(dāng)子進(jìn)程把快照內(nèi)容寫(xiě)入已命令方式寫(xiě)到臨時(shí)文件中后,子進(jìn)程發(fā)信號(hào)通知父進(jìn)程。然后父進(jìn)程把緩存的寫(xiě)命令也寫(xiě)入到臨時(shí)文件。
- 現(xiàn)在父進(jìn)程可以使用臨時(shí)文件替換老的aof文件,并重命名,后面收到的寫(xiě)命令也開(kāi)始往新的aof文件中追加。
需要注意到是重寫(xiě)aof文件的操作,并沒(méi)有讀取舊的aof文件,而是將整個(gè)內(nèi)存中的數(shù)據(jù)庫(kù)內(nèi)容用命令的方式重寫(xiě)了一個(gè)新的aof文件,這點(diǎn)和快照有點(diǎn)類(lèi)似。
3. 優(yōu)勢(shì)
- 使用 AOF 持久化會(huì)讓 Redis 變得非常耐久:你可以設(shè)置不同的 fsync 策略,比如無(wú) fsync ,每秒鐘一次 fsync ,或者每次執(zhí)行寫(xiě)入命令時(shí) fsync 。AOF 的默認(rèn)策略為每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的性能,并且就算發(fā)生故障停機(jī),也最多只會(huì)丟失一秒鐘的數(shù)據(jù)( fsync 會(huì)在后臺(tái)線(xiàn)程執(zhí)行,所以主線(xiàn)程可以繼續(xù)努力地處理命令請(qǐng)求)。
- AOF 文件是一個(gè)只進(jìn)行追加操作的日志文件, 因此對(duì) AOF 文件的寫(xiě)入不需要進(jìn)行 seek , 即使日志因?yàn)槟承┰蚨宋磳?xiě)入完整的命令(比如寫(xiě)入時(shí)磁盤(pán)已滿(mǎn),寫(xiě)入中途停機(jī),等等), redis-check-aof 工具也可以輕易地修復(fù)這種問(wèn)題。
- Redis 可以在 AOF 文件體積變得過(guò)大時(shí),自動(dòng)地在后臺(tái)對(duì) AOF 進(jìn)行重寫(xiě): 重寫(xiě)后的新 AOF 文件包含了恢復(fù)當(dāng)前數(shù)據(jù)集所需的最小命令集合。 整個(gè)重寫(xiě)操作是絕對(duì)安全的,因?yàn)?Redis 在創(chuàng)建新 AOF 文件的過(guò)程中,會(huì)繼續(xù)將命令追加到現(xiàn)有的 AOF 文件里面,即使重寫(xiě)過(guò)程中發(fā)生停機(jī),現(xiàn)有的 AOF 文件也不會(huì)丟失。 而一旦新 AOF 文件創(chuàng)建完畢,Redis 就會(huì)從舊 AOF 文件切換到新 AOF 文件,并開(kāi)始對(duì)新 AOF 文件進(jìn)行追加操作。
- AOF 文件有序地保存了對(duì)數(shù)據(jù)庫(kù)執(zhí)行的所有寫(xiě)入操作, 這些寫(xiě)入操作以 Redis 協(xié)議的格式保存, 因此 AOF 文件的內(nèi)容非常容易被人讀懂, 對(duì)文件進(jìn)行分析(parse)也很輕松。 導(dǎo)出(export) AOF 文件也非常簡(jiǎn)單: 舉個(gè)例子, 如果你不小心執(zhí)行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫(xiě), 那么只要停止服務(wù)器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重啟 Redis , 就可以將數(shù)據(jù)集恢復(fù)到 FLUSHALL 執(zhí)行之前的狀態(tài)。
4. 劣勢(shì)
- 對(duì)于相同的數(shù)據(jù)集來(lái)說(shuō),AOF 文件的體積通常要大于 RDB 文件的體積。
- 根據(jù)所使用的 fsync 策略,AOF 的速度可能會(huì)慢于 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關(guān)閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負(fù)荷之下也是如此。 不過(guò)在處理巨大的寫(xiě)入載入時(shí),RDB 可以提供更有保證的***延遲時(shí)間。
總結(jié)
對(duì)于我們應(yīng)該選擇RDB還是AOF,取決于具體的應(yīng)用場(chǎng)景,官方的建議是兩個(gè)同時(shí)使用。這樣可以提供更可靠的持久化方案。其實(shí)RDB和AOF兩種方式也可以同時(shí)使用,在這種情況下,如果redis重啟的話(huà),則會(huì)優(yōu)先采用AOF方式來(lái)進(jìn)行數(shù)據(jù)恢復(fù),這是因?yàn)锳OF方式的數(shù)據(jù)恢復(fù)完整度更高。
如果你沒(méi)有數(shù)據(jù)持久化的需求,也完全可以關(guān)閉RDB和AOF方式,這樣的話(huà),redis將變成一個(gè)純內(nèi)存數(shù)據(jù)庫(kù),就像memcache一樣。