一、前言
由於疫情的原因,學校還沒有開學,這也就讓我有了很多的時間。趁着時間比較多,我終於可以開始學習那些之前一直想學的技術了。最近這幾天開始學習Redis
,買了本《Redis實戰》
,看到了第四章,前三章都是講一些Redis
的基本使用以及命令,第四章才開始涉及到原理相關的內容。《Redis實戰》
的第四章涉及到了Redis
的持久化、主從復制以及事務等內容,我個人認為這些應該屬於Redis
中比較重要的部分,也是面試的常考內容。這篇博客就來記錄一下Redis
的持久化機制。
二、正文
2.1 為什么需要持久化
學習過Redis
的應該都知道,Redis
與MySQL
等關系型數據庫不同,它的數據不是存儲在硬盤中,而是存放在內存,所以Redis
的速度非常快。而這也就會造成一個問題:電腦如果宕機,或者由於某些原因需要重啟,此時內存中的數據就會丟失。Redis
既然把數據存放在內存,自然也就無法避免這個問題。所以,為了在電腦重啟后,能夠恢復原來的數據,Redis
就需要提供持久化的機制,將Redis
數據庫存儲在內存中的數據,在磁盤中進行備份,也就是持久化。而當Redis
重啟后,去磁盤中將持久化的數據重新讀取到內存,便能避免數據的丟失。
2.2 Redis的持久化方式
Redis
提供了兩種持久化的方式,分別是:
- 快照持久化;
- AOF持久化;
下面我就來詳細地介紹這兩種持久化的方式。
2.3 快照持久化(RDB)
快照持久化也就做RDB
持久化。快照持久化的實現方式簡單來說就是:Redis將當前內存中存儲的數據寫入到一個文件中,將這個文件作為Redis當前的一個快照,保存在磁盤中。當Redis重啟時,將這個快照文件中存儲的內容加載進內存,即可恢復Redis之前的狀態。默認情況下,Redis
將快照保存在一個叫做dump.rdb
的文件中,我們也可以在配置文件中,通過dbfilename
選項來設置快照文件的名稱;默認情況下快照文件就保存在Redis
的安裝目錄下,我們可以在配置文件中,通過dir
選項來配置快照文件的存儲路徑(其實也是AOF的路徑)。
2.4 執行快照持久化的方式
執行快照持久化有兩種方式:
2.4.1 使用配置文件
第一種方式就是在Redis
的配置文件中(windows
下這個文件叫redis.windows-service.conf
)加上save
配置項,比如像下面這樣:
save 60 100:在配置文件中加上這一條的意思是,Redis會每60秒檢查一次,若在這60秒中,Redis數據庫執行了100次以上的寫操作,那Redis就會生成一個快照文件,替換原來的快照文件;若不滿足這個條件,則不生成快照,繼續等待60秒;
我們可以根據自己的需求,調整每次等待的時間,以及對寫操作次數的要求。而且,我們可以在配置文件中,添加多個save
選項,比如一個save 60 100
,一個save 5 10
,則Redis
每5
秒以及每100
秒都會判斷一次。需要注意的是,我們不應該讓生成快照太過頻繁,因為這是一個比較消耗資源的工作,會降低Redis
的響應速度。
2.4.2 使用指令
執行快照持久化的第二個方法就是使用Redis
指令,Redis
提供了兩個指令來請求服務器進行快照持久化,這兩個指令分別是SAVE和BGSAVE。
(a)BGSAVE指令
BGSAVE
的執行流程如下:
Redis
調用系統的fork()
,創建出一個子進程;- 子進程將當前
Redis
中的數據,寫入到一個臨時文件中;同時父進程不受影響,繼續執行客戶端的請求; - 子進程將所有的數據寫入到了臨時文件后,於是使用這個文件替換原來的快照文件(默認是
dump.rdb
);
值得一提的是,通過配置文件執行快照持久化的方式,實際上就是Redis
在判斷滿足條件時,調用BGSAVE
指令來實現的。
(b)SAVE指令
SAVE
指令生成快照的方式與BGSAVE
不同,Redis
執行SAVE
指令時,不會創建一個子進程,異步的生成快照文件,而是直接使用Redis
當前進程。執行SAVE
指令在創建快照的過程中,Redis
服務器會阻塞所有的Redis
客戶端,直到快照生成完畢,並更新到磁盤之后,才會繼續執行客戶端發來的增刪改查的指令。
當然,這個指令一般很少使用,因為會阻塞客戶端,造成停頓。但是實際上,這個指令的執行效率一般比BGSAVE
更高,因為不需要創建子進程,而且在這個過程中,其他操作被阻塞,Redis
服務器一心一意地生成快照。在《Redis實戰》
中,作者提到了使用SAVE
指令的一個案例:
當前服務器的Redis數據庫中保存了大量數據,使用BGSAVE指令生成快照會非常的耗時 ,而且由於所剩內存不多,甚至無法創建子進程,於是作者編寫了一個shell腳本,這個腳本的內容就是讓服務器每天凌晨3點,執行SAVE命令,生成快照,這樣就不需要創建子進程,而且由於是凌晨三點,用戶較少,SAVE的阻塞機制也不會有太大的影響。
2.5 快照持久化的優缺點
(1)優點:
-
快照的
rdb
文件是一個經過壓縮的緊湊文件,它保存了Redis
在某個時間點上的數據集,這個文件非常適合用來備份。我們可以存儲Redis
服務器在不同時間點上的rbd
文件,比如說一小時存儲一次,一個月存儲一次,這樣就可以在遇到問題或有特殊需求時,將Redis
恢復到某一個時間點; -
RDB非常適用於災難恢復(disaster recovery):它只有一個文件,並且內容都非常緊湊,可以(在加密后)將它傳送到別的服務器上;
-
RDB 可以最大化 Redis 的性能:父進程在保存
RDB
文件時唯一要做的就是fork
出一個子進程,然后這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤I/O
操作,所以不會影響父進程處理客戶端的請求; -
RDB
在恢復大數據集時的速度比AOF
的恢復速度要快。
(2)缺點:
- 當我們的服務器發生異常,導致停機時,那我們將會丟失最后一次創建快照后,所作的所有寫操作,因為這些操作發生在內存中,還沒來得及同步到磁盤。比如我們在配置文件中配置每
5
分鍾生成一次快照,那當系統發生故障導致宕機時,我們將會丟失好幾分鍾內的操作; - 每次執行
BGSAVE
創建快照,都需要先創建出一個子進程,再由子進程執行后續操作,當內存中數據較大時,創建一個子進程將會非常耗時,造成服務器等待數毫秒,甚至達到一秒,影響用戶體驗。而且從這一點也可以說明,我們不能通過提高生成快照的頻率,來解決第一個缺點;
2.6 AOF持久化
AOF
全稱為只追加文件(append-only file),它的實現方式簡單來說就是:AOF持久化機制,會將Redis執行的所有寫指令,追加到到AOF的末尾,當服務器發生宕機,或者由於某些原因需要重啟時,在重啟后,讀取AOF文件,重新執行其中記錄的寫操作,以此達到恢復數據的目的。
AOF
持久化機制默認是關閉的,我們可以在配置文件中,配置appendonly yes來開啟。同時我們也可以通過配置appendfsync,控制寫操作追加到AOF
中的頻率,它有如下三種選項:
- always:
Redis
每次執行寫指令,都會立即將這個寫指令同步到AOF
中;使用這個選項時,Redis
發生異常,則最多只會丟失一次寫操作(也就是在同步的過程中宕機,沒同步完成),但是這也會導致Redis
的響應速度變慢,因為此選項會造成頻繁的IO
操作; - everysec(默認):每秒同步一次,使用此選項,速度足夠快,而且發生宕機時也只會丟失
1s
內的寫操作; - no:讓操作系統來決定什么時候進行同步,這個選項速度更快,但是不安全,宕機時丟失數據的多少是不確定的;
推薦(也是默認),使用第二個選項everysec
,每秒進行一次同步,因為這個選項兼顧了速度與安全性,而第一個選項太慢,第三個選項無法保證安全性。
2.7 AOF的重寫機制
AOF
持久化的執行機制就是,不斷地將寫指令追加到AOF
的末尾,這樣就會導致AOF
越來越大。為了解決AOF
越來越大的問題,Redis
實現了一種優化機制——AOF重寫。當Redis
檢查到AOF
已經很大時,就會觸發重寫機制,優化其中的內容,將它優化為能夠得到相同結果的最小的指令集合。
比如說,我們在Redis
中使用SET
指令創建了一個String
類型的數據,最后使用DEL
指令將它刪除了。如果開啟了AOF
持久化,那么則AOF
中,將會記錄這條SET
和DEL
指令。但是,在執行重寫的過程中,這個String最后被刪除了,那么Redis
就不會將這兩條指令加入新的AOF
中,因為已經被刪除的數據,不需要恢復。再比如說,我們使用Redis
維護了一個計數器cnt
,我們使用了100
次INCR
指令,讓cnt
自增到了100
,而Redis
重寫AOF
時,可以將這100
條incr
修改為一條SET
指令,直接將cnt
設置為100
,而不是保留100
條INCR
。
經過了重寫后,AOF
的大小將會大大減小,而且也去除了不必要的操作,優化了恢復數據的指令集。而AOF
重寫的過程與生成一個快照文件類似,如下:
Redis
執行fock()
,創建一個子進程用來執行后續操作,而父進程繼續處理發送到Redis
的執行請求;- 子進程重寫舊
AOF
文件,將重寫后的內容寫入到一個臨時文件; - 如果這個過程中,有新的寫指令到達,那么
Redis
會將這些寫指令依舊追加到舊的AOF
中,同時也會將這些指令加入到內存的一個緩沖區中。這樣做的目的是,如果服務器發生異常,AOF
重寫失敗,這些指令依然能夠保存在舊AOF
,不會丟失; - 當子進程完成重寫工作時,它給父進程發送一個信號,父進程在接收到信號之后,將內存緩存中的所有寫指令追加到新
AOF
文件的末尾; - 使用新
AOF
替換舊的AOF
,這之后執行的所有寫指令都將追加到新的AOF
中;
2.8 AOF的錯誤處理
AOF
文件是有可能發生錯誤的,比如上面提過,如果當前Redis
正在向AOF
中追加一個寫指令,但是此時服務器宕機,那么這個存入AOF
中的這個寫指令就是不完整的,也就是AOF
出現了錯誤。如果停機造成了 AOF
文件出錯(corrupt), 那么 Redis
在重啟時會拒絕載入這個 AOF
文件, 從而確保數據的一致性不會被破壞。
那么,當AOF
發生了錯誤,應該如何處理呢?我們可以使用如下命令:
redis-check-aof --fix:redis-check-aof用來檢測AOF是否存在錯誤,如果指定了--fix參數,那么Redis在檢測到AOF的錯誤后,會對AOF進行修復。
redis-check-aof
修復AOF
的方式非常簡單:掃描AOF文件,尋找其中不正確或不完整的指令,當發現第一個出錯的指令后,就將這個指令以及這之后的所有指令刪除。為什么需要刪除出錯指令之后的所有指令呢?因為當一條指令出錯,很有可能影響到后續的操作,導致后續操作的都是臟數據,而Redis
無法檢測哪些指令是受到影響的,所以為了保險起見,就將后續指令全部刪除。不過不用擔心,因為在大多數情況下,出錯的都是AOF
最末尾的指令。
2.9 AOF的優缺點
(1)優點:
- 使用
AOF
持久化會讓Redis
變得非常耐久,意思就是說,當發生異常導致需要重啟服務器時,只會丟失很少的一部分數據,因為AOF
持久化默認1s
同步一次,也就是說,Redis
最多只會丟失1s
中所做的修改; Redis
可以在AOF
文件體積變得過大時,自動地在后台對AOF
進行重寫: 重寫后的新AOF
文件包含了恢復當前數據集所需的最小命令集合。AOF
文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以Redis
協議的格式保存, 因此AOF
文件的內容非常容易被人讀懂, 對文件進行分析(parse
)也很輕松。AOF
文件是一個只進行追加操作的日志文件(append only log
), 因此對AOF
文件的寫入不需要進行seek
, 即使日志因為某些原因而包含了未寫入完整的命令(比如寫入時磁盤已滿,寫入中途停機,等等),redis-check-aof
工具也可以輕易地修復這種問題。
(2)缺點:
- 對於相同的數據集來說,
AOF
文件的體積通常要大於快照RDB
文件的體積大,因為RDB
只存儲數據,而AOF
中的寫指令,既包含指令,也包含數據; - 如果
AOF
體積太大,那么恢復數據將要花費較長的時間,因為需要重做指令;
2.10 選擇快照還是AOF?
說到這里,可能就有人要問了,在實際生產中,我們應該使用快照持久化還是AOF
持久化呢?
1、如果希望自己的數據庫有很高的安全性,則應該兩者同時使用,AOF
用作精確記錄,而快照用作數據備份,前面也說過,快照非常適合用來做數據備份,因為它只存儲數據庫中的數據,並且經過了壓縮,而且它恢復Redis
數據的速度一般要快於AOF
;
2、如果可以容忍一段時間內的數據丟失,則可以考慮只使用快照持久化,減小服務器的開銷;
值得一提的是,如果我們同時開啟了快照持久化和AOF
持久化,Redis
在重啟后,會優先選擇AOF
來恢復數據,因為一般情況下,AOF
能夠更加完整地恢復數據。
三、總結
快照持久化和AOF
持久化各有優劣,在實際生產環境中,我們一般是兩者配合使用,快照持久化消耗較低,而且適合用於備份,但是會丟失一段時間的數據;AOF
持久化更加地耐久,可靠性更高,但是開銷可能相對較高。以上就對Redis
的持久化機制做了一個比較詳細的介紹,相信看完只后,對Redis
會有一個更加深入的理解。
四、參考
- 《Redis實戰》
- Redis官方文檔——持久化