您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Redis中持久化原理是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
Redis持久化機制:
## 寫在前面
本文從整體上詳細介紹Redis的兩種持久化方式,包含工作原理、持久化流程及實踐策略,以及背后的一些理論知識。上一篇文章僅介紹了RDB持久化,但是Redis持久化是一個整體,單獨介紹不成體系,故重新整理。【相關推薦:Redis視頻教程】
Redis是一個內存數據庫,所有的數據將保存在內存中,這與傳統的MySQL、Oracle、SqlServer等關系型數據庫直接把數據保存到硬盤相比,Redis的讀寫效率非常高。但是保存在內存中也有一個很大的缺陷,一旦斷電或者宕機,內存數據庫中的內容將會全部丟失。為了彌補這一缺陷,Redis提供了把內存數據持久化到硬盤文件,以及通過備份文件來恢復數據的功能,即Redis持久化機制。
Redis支持兩種方式的持久化:RDB快照和AOF。
RDB快照用官方的話來說:RDB持久化方案是按照指定時間間隔對你的數據集生成的時間點快照(point-to-time snapshot)。它以緊縮的二進制文件保存Redis數據庫某一時刻所有數據對象的內存快照,可用于Redis的數據備份、轉移與恢復。到目前為止,仍是官方的默認支持方案。
既然說RDB是Redis中數據集的時間點快照,那我們先簡單了解一下Redis內的數據對象在內存中是如何存儲與組織的。
默認情況下,Redis中有16個數據庫,編號從0-15,每個Redis數據庫使用一個redisDb
對象來表示,redisDb
使用hashtable存儲K-V對象。為方便理解,我以其中一個db為例繪制Redis內部數據的存儲結構示意圖。
時間點快照也就是某一時刻Redis內每個DB中每個數據對象的狀態,先假設在這一時刻所有的數據對象不再改變,我們就可以按照上圖中的數據結構關系,把這些數據對象依次讀取出來并寫入到文件中,以此實現Redis的持久化。然后,當Redis重啟時按照規則讀取這個文件中的內容,再寫入到Redis內存即可恢復至持久化時的狀態。
當然,這個前提時我們上面的假設成立,否則面對一個時刻變化的數據集,我們無從下手。我們知道Redis中客戶端命令處理是單線程模型,如果把持久化作為一個命令處理,那數據集肯定時處于靜止狀態。另外,操作系統提供的fork()函數創建的子進程可獲得與父進程一致的內存數據,相當于獲取了內存數據副本;fork完成后,父進程該干嘛干嘛,持久化狀態的工作交給子進程就行了。
很顯然,第一種情況不可取,持久化備份會導致短時間內Redis服務不可用,這對于高HA的系統來講是無法容忍的。所以,第二種方式是RDB持久化的主要實踐方式。由于fork子進程后,父進程數據一直在變化,子進程并不與父進程同步,RDB持久化必然無法保證實時性;RDB持久化完成后發生斷電或宕機,會導致部分數據丟失;備份頻率決定了丟失數據量的大小,提高備份頻率,意味著fork過程消耗較多的CPU資源,也會導致較大的磁盤I/O。
在Redis內完成RDB持久化的方法有rdbSave和rdbSaveBackground兩個函數方法(源碼文件rdb.c中),先簡單說下兩者差別:
rdbSave:是同步執行的,方法調用后就會立刻啟動持久化流程。由于Redis是單線程模型,持久化過程中會阻塞,Redis無法對外提供服務;
rdbSaveBackground:是后臺(異步)執行的,該方法會fork出子進程,真正的持久化過程是在子進程中執行的(調用rdbSave),主進程會繼續提供服務;
RDB持久化的觸發必然離不開以上兩個方法,觸發的方式分為手動和自動。手動觸發容易理解,是指我們通過Redis客戶端人為的對Redis服務端發起持久化備份指令,然后Redis服務端開始執行持久化流程,這里的指令有save和bgsave。自動觸發是Redis根據自身運行要求,在滿足預設條件時自動觸發的持久化流程,自動觸發的場景有如下幾個(摘自這篇文章):
serverCron中save m n
配置規則自動觸發;
從節點全量復制時,主節點發送rdb文件給從節點完成復制操作,主節點會出發bgsave;
執行debug reload
命令重新加載redis時;
默認情況下(未開啟AOF)執行shutdown命令時,自動執行bgsave;
結合源碼及參考文章,我整理了RDB持久化流程來幫助大家有個整體的了解,然后再從一些細節進行說明。
從上圖可以知道:
自動觸發的RDB持久化是通過rdbSaveBackground以子進程方式執行的持久化策略;
手動觸發是以客戶端命令方式觸發的,包含save和bgsave兩個命令,其中save命令是在Redis的命令處理線程以阻塞的方式調用rdbSave
方法完成的。
自動觸發流程是一個完整的鏈路,涵蓋了rdbSaveBackground、rdbSave等,接下來我以serverCron為例分析一下整個流程。
serverCron是Redis內的一個周期性函數,每隔100毫秒執行一次,它的其中一項工作就是:根據配置文件中save規則來判斷當前需要進行自動持久化流程,如果滿足條件則嘗試開始持久化。了解一下這部分的實現。
在redisServer
中有幾個與RDB持久化有關的字段,我從代碼中摘出來,中英文對照著看下:
struct redisServer { /* 省略其他字段 */ /* RDB persistence */ long long dirty; /* Changes to DB from the last save * 上次持久化后修改key的次數 */ struct saveparam *saveparams; /* Save points array for RDB, * 對應配置文件多個save參數 */ int saveparamslen; /* Number of saving points, * save參數的數量 */ time_t lastsave; /* Unix time of last successful save * 上次持久化時間*/ /* 省略其他字段 */ } /* 對應redis.conf中的save參數 */ struct saveparam { time_t seconds; /* 統計時間范圍 */ int changes; /* 數據修改次數 */ };
saveparams
對應redis.conf
下的save規則,save參數是Redis觸發自動備份的觸發策略,seconds
為統計時間(單位:秒), changes
為在統計時間內發生寫入的次數。save m n
的意思是:m秒內有n條寫入就觸發一次快照,即備份一次。save參數可以配置多組,滿足在不同條件的備份要求。如果需要關閉RDB的自動備份策略,可以使用save ""
。以下為幾種配置的說明:
# 表示900秒(15分鐘)內至少有1個key的值發生變化,則執行 save 900 1 # 表示300秒(5分鐘)內至少有1個key的值發生變化,則執行 save 300 10 # 表示60秒(1分鐘)內至少有10000個key的值發生變化,則執行 save 60 10000 # 該配置將會關閉RDB方式的持久化 save ""
serverCron
對RDB save規則的檢測代碼如下所示:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { /* 省略其他邏輯 */ /* 如果用戶請求進行AOF文件重寫時,Redis正在執行RDB持久化,Redis會安排在RDB持久化完成后執行AOF文件重寫, * 如果aof_rewrite_scheduled為true,說明需要執行用戶的請求 */ /* Check if a background saving or AOF rewrite in progress terminated. */ if (hasActiveChildProcess() || ldbPendingChildren()) { run_with_period(1000) receiveChildInfo(); checkChildrenDone(); } else { /* 后臺無 saving/rewrite 子進程才會進行,逐個檢查每個save規則*/ for (j = 0; j < server.saveparamslen; j++) { struct saveparam *sp = server.saveparams+j; /* 檢查規則有幾個:滿足修改次數,滿足統計周期,達到重試時間間隔或者上次持久化完成*/ if (server.dirty >= sp->changes && server.unixtime-server.lastsave > sp->seconds &&(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK)) { serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds); rdbSaveInfo rsi, *rsiptr; rsiptr = rdbPopulateSaveInfo(&rsi); /* 執行bgsave過程 */ rdbSaveBackground(server.rdb_filename,rsiptr); break; } } /* 省略:Trigger an AOF rewrite if needed. */ } /* 省略其他邏輯 */ }
如果沒有后臺的RDB持久化或AOF重寫進程,serverCron會根據以上配置及狀態判斷是否需要執行持久化操作,判斷依據就是看lastsave、dirty是否滿足saveparams數組中的其中一個條件。如果有一個條件匹配,則調用rdbSaveBackground方法,執行異步持久化流程。
rdbSaveBackground是RDB持久化的輔助性方法,主要工作是fork子進程,然后根據調用方(父進程或者子進程)不同,有兩種不同的執行邏輯。
如果調用方是父進程,則fork出子進程,保存子進程信息后直接返回。
如果調用方是子進程則調用rdbSave執行RDB持久化邏輯,持久化完成后退出子進程。
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) { pid_t childpid; if (hasActiveChildProcess()) return C_ERR; server.dirty_before_bgsave = server.dirty; server.lastbgsave_try = time(NULL); // fork子進程 if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) { int retval; /* Child 子進程:修改進程標題 */ redisSetProcTitle("redis-rdb-bgsave"); redisSetCpuAffinity(server.bgsave_cpulist); // 執行rdb持久化 retval = rdbSave(filename,rsi); if (retval == C_OK) { sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB"); } // 持久化完成后,退出子進程 exitFromChild((retval == C_OK) ? 0 : 1); } else { /* Parent 父進程:記錄fork子進程的時間等信息*/ if (childpid == -1) { server.lastbgsave_status = C_ERR; serverLog(LL_WARNING,"Can't save in background: fork: %s", strerror(errno)); return C_ERR; } serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid); // 記錄子進程開始的時間、類型等。 server.rdb_save_time_start = time(NULL); server.rdb_child_type = RDB_CHILD_TYPE_DISK; return C_OK; } return C_OK; /* unreached */ }
rdbSave是真正執行持久化的方法,它在執行時存在大量的I/O、計算操作,耗時、CPU占用較大,在Redis的單線程模型中持久化過程會持續占用線程資源,進而導致Redis無法提供其他服務。為了解決這一問題Redis在rdbSaveBackground中fork出子進程,由子進程完成持久化工作,避免了占用父進程過多的資源。
需要注意的是,如果父進程內存占用過大,fork過程會比較耗時,在這個過程中父進程無法對外提供服務;另外,需要綜合考慮計算機內存使用量,fork子進程后會占用雙倍的內存資源,需要確保內存夠用。通過info stats命令查看latest_fork_usec選項,可以獲取最近一個fork以操作的耗時。
Redis的rdbSave函數是真正進行RDB持久化的函數,流程、細節賊多,整體流程可以總結為:創建并打開臨時文件、Redis內存數據寫入臨時文件、臨時文件寫入磁盤、臨時文件重命名為正式RDB文件、更新持久化狀態信息(dirty、lastsave)。其中“Redis內存數據寫入臨時文件”最為核心和復雜,寫入過程直接體現了RDB文件的文件格式,本著一圖勝千言的理念,我按照源碼流程繪制了下圖。
補充說明一下,上圖右下角“遍歷當前數據庫的鍵值對并寫入”這個環節會根據不同類型的Redis數據類型及底層數據結構采用不同的格式寫入到RDB文件中,不再展開了。我覺得大家對整個過程有個直觀的理解就好,這對于我們理解Redis內部的運作機制大有裨益。
上一節我們知道RDB是一種時間點(point-to-time)快照,適合數據備份及災難恢復,由于工作原理的“先天性缺陷”無法保證實時性持久化,這對于緩存丟失零容忍的系統來說是個硬傷,于是就有了AOF。
AOF是Append Only File的縮寫,它是Redis的完全持久化策略,從1.1版本開始支持;這里的file存儲的是引起Redis數據修改的命令集合(比如:set/hset/del等),這些集合按照Redis Server的處理順序追加到文件中。當重啟Redis時,Redis就可以從頭讀取AOF中的指令并重放,進而恢復關閉前的數據狀態。
AOF持久化默認是關閉的,修改redis.conf以下信息并重啟,即可開啟AOF持久化功能。
# no-關閉,yes-開啟,默認no appendonly yes appendfilename appendonly.aof
AOF本質是為了持久化,持久化對象是Redis內每一個key的狀態,持久化的目的是為了在Reids發生故障重啟后能夠恢復至重啟前或故障前的狀態。相比于RDB,AOF采取的策略是按照執行順序持久化每一條能夠引起Redis中對象狀態變更的命令,命令是有序的、有選擇的。把aof文件轉移至任何一臺Redis Server,從頭到尾按序重放這些命令即可恢復如初。舉個例子:
首先執行指令set number 0
,然后隨機調用incr number
、get number
各5次,最后再執行一次get number
,我們得到的結果肯定是5。
因為在這個過程中,能夠引起number
狀態變更的只有set/incr
類型的指令,并且它們執行的先后順序是已知的,無論執行多少次get
都不會影響number
的狀態。所以,保留所有set/incr
命令并持久化至aof文件即可。按照aof的設計原理,aof文件中的內容應該是這樣的(這里是假設,實際為RESP協議):
set number 0 incr number incr number incr number incr number incr number
最本質的原理用“命令重放”四個字就可以概括。但是,考慮實際生產環境的復雜性及操作系統等方面的限制,Redis所要考慮的工作要比這個例子復雜的多:
Redis Server啟動后,aof文件一直在追加命令,文件會越來越大。文件越大,Redis重啟后恢復耗時越久;文件太大,轉移工作就越難;不加管理,可能撐爆硬盤。很顯然,需要在合適的時機對文件進行精簡。例子中的5條incr指令很明顯的可以替換為為一條set
命令,存在很大的壓縮空間。
眾所周知,文件I/O是操作系統性能的短板,為了提高效率,文件系統設計了一套復雜的緩存機制,Redis操作命令的追加操作只是把數據寫入了緩沖區(aof_buf),從緩沖區到寫入物理文件在性能與安全之間權衡會有不同的選擇。
文件壓縮即意味著重寫,重寫時即可依據已有的aof文件做命令整合,也可以先根據當前Redis內數據的狀態做快照,再把存儲快照過程中的新增的命令做追加。
aof備份后的文件是為了恢復數據,結合aof文件的格式、完整性等因素,Redis也要設計一套完整的方案做支持。
從流程上來看,AOF的工作原理可以概括為幾個步驟:命令追加(append)、文件寫入與同步(fsync)、文件重寫(rewrite)、重啟加載(load),接下來依次了解每個步驟的細節及背后的設計哲學。
當 AOF 持久化功能處于打開狀態時,Redis 在執行完一個寫命令之后,會以協議格式(也就是RESP,即 Redis 客戶端和服務器交互的通信協議 )把被執行的寫命令追加到 Redis 服務端維護的 AOF 緩沖區末尾。對AOF文件只有單線程的追加操作,沒有seek等復雜的操作,即使斷電或宕機也不存在文件損壞風險。另外,使用文本協議好處多多:
文本協議有很好的兼容性;
文本協議就是客戶端的請求命令,不需要二次處理,節省了存儲及加載時的處理開銷;
文本協議具有可讀性,方便查看、修改等處理。
AOF緩沖區類型為Redis自主設計的數據結構sds
,Redis會根據命令的類型采用不同的方法(catAppendOnlyGenericCommand
、catAppendOnlyExpireAtCommand
等)對命令內容進行處理,最后寫入緩沖區。
需要注意的是:如果命令追加時正在進行AOF重寫,這些命令還會追加到重寫緩沖區(aof_rewrite_buffer
)。
AOF文件的寫入與同步離不開操作系統的支持,開始介紹之前,我們需要補充一下Linux I/O緩沖區相關知識。硬盤I/O性能較差,文件讀寫速度遠遠比不上CPU的處理速度,如果每次文件寫入都等待數據寫入硬盤,會整體拉低操作系統的性能。為了解決這個問題,操作系統提供了延遲寫(delayed write)機制來提高硬盤的I/O性能。
傳統的UNIX實現在內核中設有緩沖區高速緩存或頁面高速緩存,大多數磁盤I/O都通過緩沖進行。 當將數據寫入文件時,內核通常先將該數據復制到其中一個緩沖區中,如果該緩沖區尚未寫滿,則并不將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩沖區以便存放其他磁盤塊數據時, 再將該緩沖排入到輸出隊列,然后待其到達隊首時,才進行實際的I/O操作。這種輸出方式就被稱為延遲寫。
延遲寫減少了磁盤讀寫次數,但是卻降低了文件內容的更新速度,使得欲寫到文件中的數據在一段時間內并沒有寫到磁盤上。當系統發生故障時,這種延遲可能造成文件更新內容的丟失。為了保證磁盤上實際文件系統與緩沖區高速緩存中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數為強制寫入硬盤提供支持。
Redis每次事件輪訓結束前(beforeSleep
)都會調用函數flushAppendOnlyFile
,flushAppendOnlyFile
會把AOF緩沖區(aof_buf
)中的數據寫入內核緩沖區,并且根據appendfsync
配置來決定采用何種策略把內核緩沖區中的數據寫入磁盤,即調用fsync()
。該配置有三個可選項always
、no
、everysec
,具體說明如下:
always:每次都調用fsync()
,是安全性最高、性能最差的一種策略。
no:不會調用fsync()
。性能最好,安全性最差。
everysec:僅在滿足同步條件時調用fsync()
。這是官方建議的同步策略,也是默認配置,做到兼顧性能和數據安全性,理論上只有在系統突然宕機的情況下丟失1秒的數據。
注意:上面介紹的策略受配置項no-appendfsync-on-rewrite
的影響,它的作用是告知Redis:AOF文件重寫期間是否禁止調用fsync(),默認是no。
如果appendfsync
設置為always
或everysec
,后臺正在進行的BGSAVE
或者BGREWRITEAOF
消耗過多的磁盤I/O,在某些Linux系統配置下,Redis對fsync()的調用可能阻塞很長時間。然而這個問題還沒有修復,因為即使是在不同的線程中執行fsync()
,同步寫入操作也會被阻塞。
為了緩解此問題,可以使用該選項,以防止在進行BGSAVE
或BGREWRITEAOF
時在主進程中調用fsync()。
設置為yes
意味著,如果子進程正在進行BGSAVE
或BGREWRITEAOF
,AOF的持久化能力就與appendfsync
設置為no
有著相同的效果。最糟糕的情況下,這可能會導致30秒的緩存數據丟失。
如果你的系統有上面描述的延遲問題,就把這個選項設置為yes
,否則保持為no
。
如前面提到的,Redis長時間運行,命令不斷寫入AOF,文件會越來越大,不加控制可能影響宿主機的安全。
為了解決AOF文件體積問題,Redis引入了AOF文件重寫功能,它會根據Redis內數據對象的最新狀態生成新的AOF文件,新舊文件對應的數據狀態一致,但是新文件會具有較小的體積。重寫既減少了AOF文件對磁盤空間的占用,又可以提高Redis重啟時數據恢復的速度。還是下面這個例子,舊文件中的6條命令等同于新文件中的1條命令,壓縮效果顯而易見。
我們說,AOF文件太大時會觸發AOF文件重寫,那到底是多大呢?有哪些情況會觸發重寫操作呢?
**
與RDB方式一樣,AOF文件重寫既可以手動觸發,也會自動觸發。手動觸發直接調用bgrewriteaof
命令,如果當時無子進程執行會立刻執行,否則安排在子進程結束后執行。自動觸發由Redis的周期性方法serverCron
檢查在滿足一定條件時觸發。先了解兩個配置項:
auto-aof-rewrite-percentage:代表當前AOF文件大小(aof_current_size)和上一次重寫后AOF文件大小(aof_base_size)相比,增長的比例。
auto-aof-rewrite-min-size:表示運行BGREWRITEAOF
時AOF文件占用空間最小值,默認為64MB;
Redis啟動時把aof_base_size
初始化為當時aof文件的大小,Redis運行過程中,當AOF文件重寫操作完成時,會對其進行更新;aof_current_size
為serverCron
執行時AOF文件的實時大小。當滿足以下兩個條件時,AOF文件重寫就會觸發:
增長比例:(aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage 文件大小:aof_current_size > auto-aof-rewrite-min-size
手動觸發與自動觸發的代碼如下,同樣在周期性方法serverCron
中:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { /* 省略其他邏輯 */ /* 如果用戶請求進行AOF文件重寫時,Redis正在執行RDB持久化,Redis會安排在RDB持久化完成后執行AOF文件重寫, * 如果aof_rewrite_scheduled為true,說明需要執行用戶的請求 */ if (!hasActiveChildProcess() && server.aof_rewrite_scheduled) { rewriteAppendOnlyFileBackground(); } /* Check if a background saving or AOF rewrite in progress terminated. */ if (hasActiveChildProcess() || ldbPendingChildren()) { run_with_period(1000) receiveChildInfo(); checkChildrenDone(); } else { /* 省略rdb持久化條件檢查 */ /* AOF重寫條件檢查:aof開啟、無子進程運行、增長百分比已設置、當前文件大小超過閾值 */ if (server.aof_state == AOF_ON && !hasActiveChildProcess() && server.aof_rewrite_perc && server.aof_current_size > server.aof_rewrite_min_size) { long long base = server.aof_rewrite_base_size ? server.aof_rewrite_base_size : 1; /* 計算增長百分比 */ long long growth = (server.aof_current_size*100/base) - 100; if (growth >= server.aof_rewrite_perc) { serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth); rewriteAppendOnlyFileBackground(); } } } /**/ }
AOF文件重寫的流程是什么?聽說Redis支持混合持久化,對AOF文件重寫有什么影響?
從4.0版本開始,Redis在AOF模式中引入了混合持久化方案,即:純AOF方式、RDB+AOF方式,這一策略由配置參數aof-use-rdb-preamble
(使用RDB作為AOF文件的前半段)控制,默認關閉(no),設置為yes可開啟。所以,在AOF重寫過程中文件的寫入會有兩種不同的方式。當aof-use-rdb-preamble
的值是:
no:按照AOF格式寫入命令,與4.0前版本無差別;
yes:先按照RDB格式寫入數據狀態,然后把重寫期間AOF緩沖區的內容以AOF格式寫入,文件前半部分為RDB格式,后半部分為AOF格式。
結合源碼(6.0版本,源碼太多這里不貼出,可參考aof.c
)及參考資料,繪制AOF重寫(BGREWRITEAOF)流程圖:
結合上圖,總結一下AOF文件重寫的流程:
rewriteAppendOnlyFileBackground開始執行,檢查是否有正在進行的AOF重寫或RDB持久化子進程:如果有,則退出該流程;如果沒有,則繼續創建接下來父子進程間數據傳輸的通信管道。執行fork()操作,成功后父子進程分別執行不同的流程。
父進程:
記錄子進程信息(pid)、時間戳等;
繼續響應其他客戶端請求;
收集AOF重寫期間的命令,追加至aof_rewrite_buffer;
等待并向子進程同步aof_rewrite_buffer的內容;
子進程:
修改當前進程名稱,創建重寫所需的臨時文件,調用rewriteAppendOnlyFile函數;
根據aof-use-rdb-preamble
配置,以RDB或AOF方式寫入前半部分,并同步至硬盤;
從父進程接收增量AOF命令,以AOF方式寫入后半部分,并同步至硬盤;
重命名AOF文件,子進程退出。
Redis啟動后通過loadDataFromDisk
函數執行數據加載工作。這里需要注意,雖然持久化方式可以選擇AOF、RDB或者兩者兼用,但是數據加載時必須做出選擇,兩種方式各自加載一遍就亂套了。
理論上,AOF持久化比RDB具有更好的實時性,當開啟了AOF持久化方式,Redis在數據加載時優先考慮AOF方式。而且,Redis 4.0版本后AOF支持了混合持久化,加載AOF文件需要考慮版本兼容性。Redis數據加載流程如下圖所示:
在AOF方式下,開啟混合持久化機制生成的文件是“RDB頭+AOF尾”,未開啟時生成的文件全部為AOF格式。考慮兩種文件格式的兼容性,如果Redis發現AOF文件為RDB頭,會使用RDB數據加載的方法讀取并恢復前半部分;然后再使用AOF方式讀取并恢復后半部分。由于AOF格式存儲的數據為RESP協議命令,Redis采用偽客戶端執行命令的方式來恢復數據。
如果在AOF命令追加過程中發生宕機,由于延遲寫的技術特點,AOF的RESP命令可能不完整(被截斷)。遇到這種情況時,Redis會按照配置項aof-load-truncated
執行不同的處理策略。這個配置是告訴Redis啟動時讀取aof文件,如果發現文件被截斷(不完整)時該如何處理:
yes:則盡可能多的加載數據,并以日志的方式通知用戶;
no:則以系統錯誤的方式崩潰,并禁止啟動,需要用戶修復文件后再重啟。
Redis提供了兩種持久化的選擇:RDB支持以特定的實踐間隔為數據集生成時間點快照;AOF把Redis Server收到的每條寫指令持久化到日志中,待Redis重啟時通過重放命令恢復數據。日志格式為RESP協議,對日志文件只做append操作,無損壞風險。并且當AOF文件過大時可以自動重寫壓縮文件。
當然,如果你不需要對數據進行持久化,也可以禁用Redis的持久化功能,但是大多數情況并非如此。實際上,我們時有可能同時使用RDB和AOF兩種方式的,最重要的就是我們要理解兩者的區別,以便合理使用。
RDB是一個緊湊壓縮的二進制文件,代表Redis在某一個時間點上的數據快照,非常適合用于備份、全量復制等場景。
RDB對災難恢復、數據遷移非常友好,RDB文件可以轉移至任何需要的地方并重新加載。
RDB是Redis數據的內存快照,數據恢復速度較快,相比于AOF的命令重放有著更高的性能。
RDB方式無法做到實時或秒級持久化。因為持久化過程是通過fork子進程后由子進程完成的,子進程的內存只是在fork操作那一時刻父進程的數據快照,而fork操作后父進程持續對外服務,內部數據時刻變更,子進程的數據不再更新,兩者始終存在差異,所以無法做到實時性。
RDB持久化過程中的fork操作,會導致內存占用加倍,而且父進程數據越多,fork過程越長。
Redis請求高并發可能會頻繁命中save規則,導致fork操作及持久化備份的頻率不可控;
RDB文件有文件格式要求,不同版本的Redis會對文件格式進行調整,存在老版本無法兼容新版本的問題。
AOF持久化有更好的實時性,我們可以選擇三種不同的方式(appendfsync):no、every second、always,every second作為默認的策略具有最好的性能,極端情況下可能會丟失一秒的數據。
AOF文件只有append操作,無復雜的seek等文件操作,沒有損壞風險。即使最后寫入數據被截斷,也很容易使用redis-check-aof
工具修復;
當AOF文件變大時,Redis可在后臺自動重寫。重寫過程中舊文件會持續寫入,重寫完成后新文件將變得更小,并且重寫過程中的增量命令也會append到新文件。
AOF文件以已于理解與解析的方式包含了對Redis中數據的所有操作命令。即使不小心錯誤的清除了所有數據,只要沒有對AOF文件重寫,我們就可以通過移除最后一條命令找回所有數據。
AOF已經支持混合持久化,文件大小可以有效控制,并提高了數據加載時的效率。
對于相同的數據集合,AOF文件通常會比RDB文件大;
在特定的fsync策略下,AOF會比RDB略慢。一般來講,fsync_every_second的性能仍然很高,fsync_no的性能與RDB相當。但是在巨大的寫壓力下,RDB更能提供最大的低延時保障。
在AOF上,Redis曾經遇到一些幾乎不可能在RDB上遇到的罕見bug。一些特殊的指令(如BRPOPLPUSH)導致重新加載的數據與持久化之前不一致,Redis官方曾經在相同的條件下進行測試,但是無法復現問題。
對RDB和AOF兩種持久化方式的工作原理、執行流程及優缺點了解后,我們來思考下,實際場景中應該怎么權衡利弊,合理的使用兩種持久化方式。如果僅僅是使用Redis作為緩存工具,所有數據可以根據持久化數據庫進行重建,則可關閉持久化功能,做好預熱、緩存穿透、擊穿、雪崩之類的防護工作即可。
一般情況下,Redis會承擔更多的工作,如分布式鎖、排行榜、注冊中心等,持久化功能在災難恢復、數據遷移方面將發揮較大的作用。建議遵循幾個原則:
不要把Redis作為數據庫,所有數據盡可能可由應用服務自動重建。
使用4.0以上版本Redis,使用AOF+RDB混合持久化功能。
合理規劃Redis最大占用內存,防止AOF重寫或save過程中資源不足。
避免單機部署多實例。
生產環境多為集群化部署,可在slave開啟持久化能力,讓master更好的對外提供寫服務。
備份文件應自動上傳至異地機房或云存儲,做好災難備份。
通過上面的分析,我們都知道RDB的快照、AOF的重寫都需要fork,這是一個重量級操作,會對Redis造成阻塞。因此為了不影響Redis主進程響應,我們需要盡可能降低阻塞。
降低fork的頻率,比如可以手動來觸發RDB生成快照、與AOF重寫;
控制Redis最大使用內存,防止fork耗時過長;
使用更高性能的硬件;
合理配置Linux的內存分配策略,避免因為物理內存不足導致fork失敗。
關于“Redis中持久化原理是什么”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。