目錄
認識持久化
持久化方案
RDB(Redis DataBase)
手動觸發
自動觸發
小結
AOF(Append-Only File)
AOF緩沖區刷新機制
AOF重寫機制
AOF重寫流程
?編輯
混合持久化
認識持久化
我們都知道Mysql有四大特征,原子性,持久性,隔離性,一致性。其中持久性就是因為mysql會把數據存到硬盤中,即使計算機掉電數據也不會丟失。如果把數據存儲到內存中,那么就是不持久的了。而我們的redis剛好就是這樣,所以要想讓redis持久化,就要想辦法把redis的數據存儲到硬盤上。
但是redis的一個很重要的特點就是效率高,而效率高又離不開將數據存儲到內存,所以該怎么辦呢?答案就是‘我全都要’,將數據同時存儲在內存和硬盤中,當重新打開機器后只需要從硬盤中把數據讀取到內存即可,這樣既保證了效率右讓redis可以持久化儲存數據,不過這樣的方法必然會付出更大的硬盤資源。?
持久化方案
redis提供的持久化方式主要有兩種RDB和AOF
RDB(定期備份):redis會定期將內存里的所有數據都寫入硬盤中,并生成一個快照。
AOF:redis會將每個寫操作記錄下來當重啟后會將這些操作自動再執行一遍
RDB(Redis DataBase)
?redis會定期將內存里的所有數據都寫入硬盤中,并生成一個快照。這個快照就類似于一張照片,將這一時刻的數據和狀態以文件的形式寫到硬盤上。
這個定期又分為兩種方式手動觸發和自動觸發
手動觸發
程序員通過特定的命令來手動觸發快照
- save:當redis執行save操作時,就會全力以赴的進行快照生成,也就會阻塞其他redis的客戶端命令,就可能會使redis直接阻塞(一般不建議使用)
- bgsave(background save):這個操作的效果和save一樣,但是不會阻塞其他命令,因為此處redis采取了多進程的方式,會單獨生成一個子進程來完成快照生成的操作,而父進程則繼續處理客戶端的請求
bgsave的執行過程
- 首先判定當前是否還存在其他的子進程,如果已經在執行一個bgsave命令的話,這個bgsave就會直接返回,保證只有一個子進程
- 如果沒有其他子進程,就會通過fork操作創建出一個子進程來
- 子進程復制寫文件,生成快照的過程,而父進程則是繼續接受客戶端請求
- 最后zjc完成持久化后,會通知父進程,父進程更新一些統計信息后,子進程就可以銷毀了
//fork是linux提供的創建子進程的api,如果在其他系統上則行不通,fork會直接將父進程克隆一份,包括pcd,內存中的數據,文件描述符表等等,所以克隆出來的子進程就和父進程是完全一樣的,此時就可以讓父進程繼續處理客戶端請求,讓子進程去進行持久化操作,因為內存數據完全一樣,所以將子進程持久化就相當于持久化了父進程。而克隆完后的兩個進程是互相獨立,互不干擾的。
但是你會不會有疑問,完全拷貝下來如果有100G的內存那么開銷豈不是很大?實際上,redis這里并不是直接無腦的拷貝,而是利用’寫時拷貝‘,也就是如果子進程和父進程的內存數據完全一樣,那么這兩個進程共用一份內存,也就是不會觸發真正的拷貝操作,只是在效果上像是兩份,當有一方對內存數據進行了修改才會觸發真正的拷貝操作,比如當fork拷貝進程時父進程來了一條指令對數據進行了修改,那么就會將數據拷貝到子進程。
舉個例子假如父進程有一個int i = 1;那么子進程并不會拷貝而是和父進程共用這個數據的內存地址,如果在拷貝的過程中父進程發生了修改變成了int i = 2;那么fork就會觸發真正的拷貝,將原來的int i = 1復制到一個新的內存空間,而原來的則被修改為int i = 2。(一般整個持久化過程很快,父進程不會有很多數據發生改變)
這樣既不會有很大的開銷,也讓這兩個進程可以互相獨立。
當子進程寫入數據時會把數據寫入redis的工作目錄里的dump.rdb文件,這個文件是rdb機制生成的鏡像文件,而且只一個二進制的文件無法用一般的文本編輯器查看,當寫入完成后就會把新的rdb文件替換舊的rdb文件。如果是save則不會觸發文件替換而是直接在當前進程中往剛才的文件中寫入數據
具體過程是當執行rdb鏡像操作時,會把要生成的快照數據,先保存到一個臨時文件中,當快照整成完畢后,再刪除之前的rdb文件,把新生成的臨時的rdb文件名字改成剛才的dump.rdb文件
自動觸發
RDB會在Redi數據發生修改時自動保存快照進行持久化操作,不過生成一次rdb快照是一次成本比較大的操作,所以肯定不能每次修改都生成一次快照。redis是根據配置文件中的save選項來決定什么時候自動觸發持久化操作的,當達到選項中的條件后redis就會自動進行快照生成。
解釋:
save選項有三個,分別是
- 距離上次更新超過了900秒在這段時間內進行1次修改操作后進行自動觸發
- 距離上次更新超過了300秒在這段時間內進行10次修改操作后進行自動觸發
- 距離上次更新超過了60秒在這段時間內進行10000次修改操作后進行自動觸發
//如果給save設置成' ',就代表取消自動觸發
從選項中可以看到,最快的生成快照的時間是60秒,換句話說redis每兩次快照之間的最短時間是60秒,假如現在完成一次快照生成,此時快照和內存的數據是一樣的,但是redis突然進行了大量的修改,不過時間還沒到60秒,如果這時redis突然掛了或者機器掉電,那么在這段時間內的數據因為還沒來得及進行持久化操作就會直接丟失掉。
//如果是通過正常流程重新啟動redis服務器,此時redis服務器會在退出的時候自動觸發生成rdb操作,但是如果是異常重啟(kill-9或者服務器掉電)此時服務器來不及生成rdb,內存中尚未保存到快照中的數據就會隨著重啟丟失
小結
- RDB是一個緊湊的二進制文件,代表redis某一個時間點上的數據快照,非常適用于備份,全量復制等場景。比如固定時間執行bgsave,并把RDB文件復制到遠程機器或者文件中用于災難恢復
- redis加載RDB文件的速度比AOF的方式快,因為RDB使用的是二進制的方式組織數據,可以直接把數據讀到內存中,按照字節的格式取出來,而AOF則是使用文本的方式。
- RDB沒法做到實時持久化,因為redis每次都要執行fork創建子進程,開銷較大,也因此RDB可能會有數據丟失的風險,當數據來沒來得及持久化redis就掛掉或者掉電,這部分數據就會丟失
AOF(Append-Only File)
AOF是Redis的一種替代的、完全持久化的策略。AOF機制的作用是,每當redis客戶端執行寫操作時,都會被記錄再aof文件里通過特殊符號區分,如果發生服務器掛掉或者掉電后重啟,redis就可以通過將這些命令再執行一遍而達到恢復數據的效果。
AOF的啟動需要在配置文件里將appendonly.aof選項改成yes默認情況下是no,而且當啟動aof機制時rdb機制是關閉的
AOF緩沖區刷新機制
引入AOF之后,redis既要寫內存,又要寫硬盤會不會影響效率?
AOF并不會對效率有太大的影響,實際上AOF機制并不是直接將數據寫入硬盤,而是先寫入內存中的一個緩沖區,積累了一定量的數據后再一次性寫入硬盤,這樣做就大大降低了redis寫硬盤的次數(寫硬盤的次數要比一次寫多少數據對效率的影響大得多)。而且AOF是把每次的操作寫在文件的末尾是順序寫入,要比隨機寫入快得多。
雖然通過剛剛的方法大大提高了AOF的效率,但是同時也帶來了一個問題,緩沖區說到底還是在內存中,如果AOF正在將指令寫入緩沖區時,redis掛掉或者掉電,那么此時緩沖區中的數據還沒來得及寫入硬盤中,也就全部丟失掉了。雖然緩沖區的方法可以提高效率,但是相應的也會來帶來一些數據可靠性的問題,所謂魚和熊掌不可兼得。
Redis提供了多種AOF緩沖區刷新策略,由參數appedfsync控制,可配置項如下:
- always:每寫入一個命令到緩沖區,就放入硬盤,刷新緩沖區的頻率最高,數據可靠性最高,性能最低
- everysec(默認):每秒清理一次緩沖區,刷新緩沖區頻率低一些,數據可靠性也降低,性能相應的也提高了
- no:redis不進行刷新緩沖區,由操作系統自行刷新,刷新頻率最低,數據可靠性最低,性能最高
AOF重寫機制
隨著我們的服務器一直運行,aof文件也會越來越大,雖然這個文件大小對aof效率沒有什么影響,但是會影響redis的開機速度,redis啟動就會讀取這個文件里記錄的指令操作如果文件較大,執行的操作就變多,啟動之間也就變長。雖然aof文件變大是不可避免的,但是這個文件里會有很多多余的內容。比如
如果redis客戶端執行指令,lpush key?aaa,lpush?key bbb,lpush?key ccc,就相當于只執行了lpush key aaa bbb ccc。再或者一些數據插入后又刪除,這樣的操作對于結果顯然是沒用的,而redis啟動時卻會執行一遍,也就拖長了啟動時間。所以實際上我們的aof不在乎中間過程,只在乎最終的數據結果。
所以aof就針對上面問題做出了應對,也就是重寫機制,可以將aof中一些多余的操作指令給剔除掉,并且合并一些操作,從而縮小aof文件。
重寫機制的觸發方式也分為手動觸發和自動觸發
- 手動觸發:調用bgrewriteaof命令redis就會對文件進行重寫
- 自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage的參數決定觸發時機,auto-aof-rewrite-min-size表示觸發重寫時AOF的最小文件大小(默認是64mb)。auto-aof-rewrite-percentage代表AOF占用大小相比上次重寫時增加的比例。
AOF重寫流程
aof重寫的方法和rdb很像,也是先fork一個子進程,子進程負責AOF文件的重寫父進程依舊繼續負責處理redis客戶端的命令。但是我們重寫的時候并不關心當前aof文件里都有啥,而只在乎內存中的數據狀態,子進程只需要獲取當前內存中的數據,然后以aof的形式寫入一個新的aof文件,然后替換掉舊的文件即可。
這里的方法和RDB的快照也挺像,都是記錄當前時間點的內存狀態。只不過RDB是以二進制的形式,而AOF是以文本形式。
在fork創建子進程的一瞬間,子進程就繼承了父進程當前的內存狀態,但是重寫也要一定時間,可能在重寫的時候redis客戶端會對內存進行一些修改
換句話說,我們的子進程記錄的是fork之前的內存,但是fork之后的數據并不會丟失,父進程會再準備一個緩沖區aof_rewrite_buf當創建完子進程后,父進程就會將一些aof數據先存儲到這個緩沖區。
子進程完成重寫后會通知父進程,然后父進程再將aof_rewrite_buf中的數據寫入新的aof文件,最后再讓新的aof文件代替舊的aof文件
假如在redis正在重寫的時候,又來了一個重寫操作會怎么樣??
答案是,新的重寫操作會直接返回不執行,而且如果在要進行AOF操作時發現redis正在進行RDB快照操作,那么aof將會阻塞等待,等到RDB操作完成后再啟動AOF操作。
父進程在重寫的時候依然維護寫入舊的aof文件是否有必要?
?答案是,有必要因為假如redis在進行重寫操作時,這時redis服務器掛掉了的話,新的aof文件還沒有寫入完全,緩沖區里的數據也都丟失,如果舊的aof文件再被銷毀的話就會造成一部分數據的丟失。
混合持久化
雖然aof是以文本方式寫入,但是當我們查看aof文件時會發現這里是用二進制存儲,這是因為aof原本按照文本方式寫入文件但是這樣的成本較高,于是redis就引入混合持久化,也就是同時結合了rdb和aof。
按照aof的方式每個請求操作都寫入文件,觸發aof重寫后,就會把當前內存狀態按照rdb的二進制格式寫入到新的aof文件中,后續在進行的操作還是按照aof文本的方式追加到文件后面。
混合持久化在配置文件里默認開啟,當同時存在aof文件和rdb快照時,則以aof為主