快取資料丟了,原來是Redis持久化沒玩明白
我們都知道Redis是微服務架構中重要的基礎資料庫中介軟體,透過Redis可以將資料庫中的資料快取到記憶體中,當服務端有資料查詢請求的時候,可以直接從記憶體中獲取資料。如此,一方面服務端可以獲得比較快的資料請求響應,另一方面降低了後端關聯式資料庫的業務請求壓力。但是正所謂尺有所短,寸有所長,Redis最大的優勢就是記憶體資料也是最大的劣勢,因為一旦伺服器當機或者伺服器重啟,記憶體中快取的資料也會丟失。針對這樣的場景,Redis提供了三種資料持久化機制,分別是AOF、RDB以及混合持久化來應對這種異常情況。本文主要從Redis實現持久化遇到的問題出發,站在設計者的角度思考相關問題的解決思路。
AOF持久化
AOF持久化方式,即Append Only File,Redis透過記錄執行修改操作命令這種記小本本的方式進行記憶體資料持久化。當需要透過AOF日誌進行恢復資料時,Redis服務端啟動後可以從日誌檔案中回放執行命令來實現記憶體資料恢復。當然了,AOF日誌中記錄的都是修改的命令,查詢命令不會修改資料所以不需要進行記錄。
可能大家都比較熟悉WAL(Write Ahead Log),即日誌預寫機制,它是資料庫非常常用的確保資料操作原子性以及永續性的技術手段。拿Mysql舉栗子,Mysql的WAL體現在undo log以及redo log等這些日誌檔案中,資料庫在執行修改操作的時候並不是立刻將資料更新到磁碟上,而是先記錄在日誌中,主要目的是如果出現異常,可以直接從redo log中進行資料恢復,也就是說讓Mysql知道上次意外發生的時候操作到底有沒有成功,另外還可以將Mysql的隨機寫轉換為順序寫,提升IO效能。但是AOF卻不同,它是在Redis將資料寫入記憶體之後,再將相關的操作命令寫入AOF檔案中。
那麼問題來了,為什麼Redis要採取這種獨特的資料記錄方式,而不是業界常用的WAL的方式呢?其實可以從以下兩個層面思考原因。
(1)AOF檔案中儲存了執行快取的命令,以便於保證在需要恢復資料的時候可以進行命令重放恢復資料,因此需要保證執行命令的合法性,而透過先快取資料再進行命令追加日誌的方式可以確保追加到AOF檔案中的的命令都是合法有效的,redis在恢復資料的時候不需要再去檢查命令是否有效,進一步提升記憶體資料恢復的效率。
(2)另外由於是在修改操作命令之後進行日誌記錄,日誌記錄的時候需要進行磁碟IO操作,因此不會阻塞當前的修改命令。
AOF檔案內容是什麼?
redis> SET mufeng handsome
OK
Redis客戶端與服務端之間採用RESP協議進行通訊,它是一種應用層協議,對於Redis這種以效率為追求目標的中介軟體,通訊協議必定要簡單高效。就上面一條快取操作命令來說:set mufeng handsome 對應的RESP報文就是*3$3set$6mufeng$8handsome,為了方便檢視進行了手動換行。
我們來拆解下報文中各個屬性的含義,“*3”代表本次操作命令將由三個分佈組成,每一部分都是透過"$數字"的形式作為起始,後面為對應的命令、鍵或者值。如此處的"$6"就表示後面的命令是一個6個位元組的鍵值。所以,appenonly.aof檔案中實際儲存的就是這種格式的內容。
AOF有沒有丟資料的風險?
上文說到Redis透過AOF檔案實現記憶體資料持久化,那麼是不是就代表快取資料儲存就萬無一失了?這樣的持久化方式還有沒有資料丟失的風險呢?大家可以設想一下假設在操作完Redis之後,還沒來得及將命令寫入AOF檔案就當機了,那麼這個操作命令就會丟失,對應的快取資料最新值也會丟失。因為即便當機異常恢復之後,也沒辦法從AOF檔案中執行丟失的操作命令了。因此,寫入AOF緩衝區的資料什麼時候進行持久化落盤,直接決定著AOF持久化方式快取資料丟失的風險大小。
三種AOF落盤策略
針對AOF快取中的資料在什麼時機寫入磁碟,Redis提供了三種AOF日誌寫入策略供使用者進行選擇,透過後臺執行緒執行不同時機的AOF檔案資料同步操作,在redis.conf配置檔案中的配置項appendfsync可以進行配置。
每執行一個修改命令,都需要將修改的命令進行落盤操作。
具體採取怎樣的配置策略還是要根據實際的業務場景來決定,一般推薦使用第二種配置策略【appendfsync:everysec】,在可靠性以及效能方面相對平衡一點。
AOF檔案會越來越大嗎?
在瞭解了AOF日誌磁碟寫入時機之後,我們繼續來思考下一個問題。無論採取什麼樣的同步資料策略,最終都是要將修改命令寫入AOF檔案中,因此隨著時間的推移,這個檔案必定會越來越大。那麼如果檔案變得很大之後,無論是檔案資料新寫入還是Redis透過AOF檔案進行資料恢復,大檔案的操作都會造成IO效能損耗。假如你是Redis的設計者,如果遇到這種情況你會怎麼進行設計最佳化呢?我想無非有兩個最佳化思路,一個是化整為零,一個是想辦法縮小大檔案。
化整為零
當單個檔案過大時,我們很容易想到的最佳化方法就是將這個大檔案拆分為若干個小檔案。這就好比系統中一旦出現過千萬資料庫表的時候,我們就要結合實際的業務場景考慮要不要進行分庫分表了。所以如果單個AOF檔案太大,那麼是不是可以考慮將其按照固定大小進行拆分,這樣可以避免單個AOF檔案過大的問題。那麼Redis小於7.0版本為什麼沒有采用這種方案呢?主要是這種方案並不符合Redis追求簡單高效的設計思想。假設採用了這種資料分塊的方式,那必定需要實現檔案大小檢測、檔案建立、檔案索引維護等等一系列技術細節問題,對於低版本的Redis來說這些都太繁瑣了,還不如一個AOF檔案來的爽快。
AOF重寫
什麼是fork?
fork函式是linux核心提供給使用者建立程式的API,應用程式透過呼叫fork函式建立子程式,這個子程式可以和原來父程式幹同樣的事情,也可以和原來主程式幹不同的事情,這主要取決於對應的引數。這個過程就好比孫悟空拔了一根自己的猴毛變出來一個和自己一模一樣的孫悟空。
父程式fork子程式的時候,子程式擁有獨立的虛擬記憶體空間,那麼對應的實體記憶體空間是不是也是獨立的呢?我們都知道在計算機中,記憶體屬於非常寶貴的系統資源,所以大佬們在設計的時候都儘可能的減少記憶體空間佔用從而提高系統資源利用率。fork子程式過程中用到的Copy-On-Write就是典型的記憶體資源管理最佳化機制,如果子程式只是讀取資料不進行任何的資料寫入,那麼就和父程式公用記憶體空間。當子程式需要進行資料寫入的時候,發現沒有內控空間可以寫入,此時會觸發一個系統中斷來分配記憶體空間給子程式進行資料寫入。
【Condition1】:檢測當前是否存在已經在執行的AOF重寫子程式,如果存在的話Redis將不再執行AOF檔案重寫。
【Condition2】:檢測當前是否存在已經在建立RDB檔案的子程式,如果存在的話Redis將AOF檔案重寫任務置為待排程狀態,後續如果滿足了重寫條件,則繼續執行AOF檔案重寫任務。
也就是說,Redis檢測到當前既沒有AOF重寫子程式也沒有RDB檔案建立子程式,那麼就可以進行AOF檔案重寫。對應原始碼如下:
//of_child_pid(aof rewrite程式pid)、rdb_child_pid(rdb dump程式pid)
void bgrewriteaofCommand(redisClient *c) {
if (server.aof_child_pid != -1) {
//如果正在aof rewrite,返回錯誤資訊
addReplyError(c,"Background append only file rewriting already in progress");
} else if (server.rdb_child_pid != -1) {
//如果正在rdb dump,為了避免磁碟壓力,將aof重寫計劃狀態置為1,後期再進行rewrite;
server.aof_rewrite_scheduled = 1;
addReplyStatus(c,"Background append only file rewriting scheduled");
}
//如果當前沒有aof rewrite和rdb dump在進行,則呼叫rewriteAppendOnlyFileBackground開始aof rewrite。
else if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
addReplyStatus(c,"Background append only file rewriting started");
} else {
//出現異常返回錯誤。
addReply(c,shared.err);
}
}
超出配置閾值
如果Redis例項開啟了AOF配置,同時配置了auto-aof-rewrite-percentage以及auto-aof-rewrite-min-size,如果超出了閾值會觸發AOF重寫。
//沒有rdb子程式、沒有aof重寫子程式、aof檔案設定了閾值以及aof檔案大小絕對值超過閾值
if (server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
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_rewrite_scheduled被設定為待排程狀態
(1)Redis主程式首先檢查是不是存在rdb dump程式或者aof重寫程式正在執行,如果不存在Redis主程式fork子程式進行aof檔案重寫;
(2)fork出來的子程式和原來的Redis主程式擁有同樣的記憶體資料,子程式遍歷此時的記憶體資料同時將記憶體資料寫入到臨時的AOF檔案中;
(3)主程式此時仍然可以接收客戶端請求,同時將新的快取操作寫入aof_buf以及aof_rewrite_buf中,根據對應的同步策略,將buf中的資料分別寫入舊AOF檔案以及臨時AOF檔案中;
(4)重寫完成之後,臨時AOF檔案將替換原有的老的AOF檔案,從而完成整個AOF重寫。
2、在檔案恢復場景下,AOF要比DRB恢復資料慢一些。
RDB持久化
RDB(Redis Data Base),所謂的Redis記憶體資料快照就是某一時刻Redis存於記憶體中的所有快取資料,這就好比用手機相機拍照,記錄當時的美好畫面。Redis可以實現在固定時間間隔後將記憶體中的快取資料持久化儲存起來。這樣即便是伺服器當機或者重啟了,只要RDB快照檔案還存在,快照檔案中對應的快取資料就不會丟失,Redis重新啟動後會重新載入RDB檔案到記憶體中,快速恢復快取資料,透過這樣的方式保障了快取資料的可靠性。
RDB檔案生成過程
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
long long start;
// 如果已經存在aof重寫子程式以及rdb生成子程式則直接返回錯誤
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
...
// fork子程式進行RDB檔案生成
if ((childpid = fork()) == 0) {
...
// 生成RDB檔案
retval = rdbSave(filename,rsi);
if (retval == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1);
if (private_dirty) {
serverLog(LL_NOTICE,
"RDB: %zu MB of memory used by copy-on-write",
private_dirty/(1024*1024));
}
server.child_info_data.cow_size = private_dirty;
// 通知父程式RDB檔案生成完畢
sendChildInfo(CHILD_INFO_TYPE_RDB);
}
//子程式退出
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
//父程式業務邏輯
...
}
return C_OK;
}
(2)Redis主程式fork子程式進行RDB檔案生成操作,在fork的過程中,此時的Redis主程式是阻塞的,不能響應客戶端請求,子程式fork完成之後可以繼續響應客戶端請求。
(3)fork出來的子程式遍歷記憶體資料進行RDB檔案生成操作。
(4)如果此時客戶端的請求需要修改快取資料,那麼如上面fork子程式的原理,透過COW機制,作業系統會開闢新的記憶體空間給Redis主程式進行新的快取資料寫入。
(5)子程式快照資料生成完成之後,替換原來老的RDB檔案。
RDB觸發時機
Redis主要支援兩種持久化操作來生成RDB檔案,分別是save、bsave命令方式手動生成以及在配置檔案中配置時間間隔自動進行RDB檔案生成。
客戶端連線到redis之後我們可以透過save以及bsave命令進行RDB檔案的立即建立,兩者的區別如下:
save:透過主執行緒觸發,會阻塞Redis業務,如果記憶體資料比較多的話,會導致長時間不能響應外部請求;
bsave:客戶端執行bsave命令進行RDB持久化,Redis主執行緒會fork子執行緒出來進行RDB檔案持久化操作,這樣避免了主執行緒的阻塞即便正在持久化操作依然可以響應外部資料快取請求。
不過這裡值得注意的是,雖然fork子程式之後不會阻塞主程式,但是在fork的過程中會阻塞主程式,尤其是在記憶體資料比較大的時候,阻塞主程式的時間會更長。
配置自動觸發
另外在Redis的配置檔案redis.conf中,我們可以配置按照一定的時間間隔來進行RDB持久化操作。如下配置:
save 900 1
save 300 10
save 60 10000
RDB有沒有丟資料的風險?
由於Redis儲存RDB快照檔案的策略是按照配置的時間間隔進行持久化儲存,也就是每隔一個時間間隔Redis就會儲存一個RDB檔案。因此在記憶體資料有更新但是RDB儲存時間尚未到來的這段時間如果存在伺服器當機或者伺服器重啟的情況,此時記憶體的資料就會存在丟失的風險,因為Redis還沒來得及將資料持久化到RDB檔案中。
場景1中最大的問題就RDB檔案持久化存在時間間隔,而這個時間間隔導致了新增的快取資料存在丟失的風險。那麼是不是將時間間隔降低到最小就可以了,比如一秒鐘,即使在這一秒鐘期間出現異常情況,那快取資料也只是丟掉這一秒鐘的快取資料,相對來說資料丟失的情況可控一點。但是問題是如果真的每隔1s就儲存一個RDB檔案到伺服器磁碟中,那不論是對Redis本身還是Redis所在的伺服器磁碟IO都是一種負擔。
RDB模式優點
4、可以根據不同的時間間隔儲存RDB檔案,在恢復資料的時候可以更加靈活地選擇對應版本資料進行恢復。
RDB模式缺點
1、由於RDB資料儲存存在一定的時間間隔,因此存在丟失快取資料的風險;
混合持久化
既然AOF以及RDB持久化都有這樣或者那樣的不足,那麼有沒有一種持久化方案可以兼顧二者的優點來揚長避短呢?從4.0版本開始,Redis支援混合持久化的方式來兼顧效率以及資料可靠性。在Redis配置檔案redis.conf中配置混合持久化:
aof‐use‐rdb‐preamble yes
如果配置了混合持久化,那麼Redis主程式在fork子程式進行持久化操作的時候,原先的將記憶體資料轉換為操作命令的過程將替換為使用進行AOF重寫時對應的RDB檔案內容直接放入到重寫後的臨時檔案中,後面再有新的操作命令,都追加到臨時aof檔案中,重寫完成後使用臨時aof檔案替換舊的檔案。
混合持久化模式優點
1、同時擁有RDB以及AOF機制的優點,在資料可靠性以及資料恢復效率上面達到了很好的平衡。
混合持久化模式缺點
1、由於Redis從4.0版本才開始支援混合持久化,如果當前平臺中的Redis版本低於4.0,那麼就無法使用這個持久化機制,因此相容性不夠友好;
總結
本文主要分析了Redis AOF、RDB以及混合持久化的記憶體資料持久化的機制原理,同時分析了兩種持久化方式的優點以及缺點。我想只有理解了中介軟體的特性機制原理,知道了特性的長處以及不足我們才能設計適合我們平臺的快取資料持久化策略,從而提升平臺的穩定性。
另外在一些優秀中介軟體的學習和使用過程中,我們不能僅僅停留在會用的層面,更應該深入底層領會其架構和實現機制的設計思路,只有搞明白設計思路,時刻站在設計者的角度來看待遇到的問題,那麼在我們的實際工作中,如果遇到類似的問題我們可以借鑑這些優秀中介軟體的解決思路來進行問題分析。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2929066/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 微服務 - Redis快取 · 資料結構 · 持久化 · 分散式 · 高併發微服務Redis快取資料結構持久化分散式
- Redis篇:持久化、淘汰策略,快取失效策略Redis持久化快取
- Redis 資料持久化Redis持久化
- Redis資料持久化—RDB持久化與AOF持久化Redis持久化
- PB級資料持久化快取系統——lest持久化快取
- Redis的資料持久化Redis持久化
- 老司機帶你玩轉面試(1):快取中介軟體 Redis 基礎知識以及資料持久化面試快取Redis持久化
- 前端持久化快取最佳化前端持久化快取
- 一文讓你明白Redis持久化(RDB、AOF)Redis持久化
- Redis基礎—瞭解Redis是如何做資料持久化的Redis持久化
- 你對Redis持久化了解多少?一篇文章讓你明白Redis持久化Redis持久化
- Redis快取何以一枝獨秀?(2) —— 聊聊Redis的資料過期、資料淘汰以及資料持久化的實現機制Redis快取持久化
- Redis——Redis用作資料庫(持久化/RDB/AOF)Redis資料庫持久化
- Spark Streaming(六):快取與持久化Spark快取持久化
- redis原始碼分析(五):資料持久化Redis原始碼持久化
- 用於Electron/Nodejs的資料持久快取庫NodeJS快取
- Python操作Redis快取資料庫PythonRedis快取資料庫
- 3分鐘整明白啥是 快取雪崩快取
- 什麼是redis快取雪崩、快取穿透、快取擊穿Redis快取穿透
- 深度解析webpack5持久化快取Web持久化快取
- redis持久化的取捨和選擇Redis持久化
- redis快取優化案例Redis快取優化
- redis 是如何做持久化的Redis持久化
- 當機了,Redis資料丟了怎麼辦?Redis
- Redis快取資料庫-快速入門Redis快取資料庫
- redis持久化Redis持久化
- Redis 持久化Redis持久化
- [Redis]持久化Redis持久化
- Redis - 持久化Redis持久化
- 什麼是redis的快取雪崩與快取穿透Redis快取穿透
- Redis系列2:資料持久化提高可用性Redis持久化
- Redis不僅僅是快取,還是……Redis快取
- Redis快取篇(一)Redis是如何工作的Redis快取
- Redis中快取二進位制資料Redis快取
- 資料湖還沒玩明白,就別想著湖倉一體了!
- Redis:持久化篇Redis持久化
- redis-持久化Redis持久化
- redis 之 持久化Redis持久化