一、前言
由於疫情的原因,學校還沒有開學,這也就讓我有了很多的時間。趁著時間比較多,我終於可以開始學習那些之前一直想學的技術了。最近這幾天開始學習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官方文件——持久化