新聞中心
我們都知道Redis是微服務(wù)架構(gòu)中重要的基礎(chǔ)數(shù)據(jù)庫中間件,通過Redis可以將數(shù)據(jù)庫中的數(shù)據(jù)緩存到內(nèi)存中,當(dāng)服務(wù)端有數(shù)據(jù)查詢請(qǐng)求的時(shí)候,可以直接從內(nèi)存中獲取數(shù)據(jù)。如此,一方面服務(wù)端可以獲得比較快的數(shù)據(jù)請(qǐng)求響應(yīng),另一方面降低了后端關(guān)系數(shù)據(jù)庫的業(yè)務(wù)請(qǐng)求壓力。但是正所謂尺有所短,寸有所長,Redis最大的優(yōu)勢(shì)就是內(nèi)存數(shù)據(jù)也是最大的劣勢(shì),因?yàn)橐坏┓?wù)器宕機(jī)或者服務(wù)器重啟,內(nèi)存中緩存的數(shù)據(jù)也會(huì)丟失。針對(duì)這樣的場(chǎng)景,Redis提供了三種數(shù)據(jù)持久化機(jī)制,分別是AOF、RDB以及混合持久化來應(yīng)對(duì)這種異常情況。本文主要從Redis實(shí)現(xiàn)持久化遇到的問題出發(fā),站在設(shè)計(jì)者的角度思考相關(guān)問題的解決思路。

目前累計(jì)服務(wù)客戶1000多家,積累了豐富的產(chǎn)品開發(fā)及服務(wù)經(jīng)驗(yàn)。以網(wǎng)站設(shè)計(jì)水平和技術(shù)實(shí)力,樹立企業(yè)形象,為客戶提供成都網(wǎng)站制作、網(wǎng)站建設(shè)、網(wǎng)站策劃、網(wǎng)頁設(shè)計(jì)、網(wǎng)絡(luò)營銷、VI設(shè)計(jì)、網(wǎng)站改版、漏洞修補(bǔ)等服務(wù)。創(chuàng)新互聯(lián)始終以務(wù)實(shí)、誠信為根本,不斷創(chuàng)新和提高建站品質(zhì),通過對(duì)領(lǐng)先技術(shù)的掌握、對(duì)創(chuàng)意設(shè)計(jì)的研究、對(duì)客戶形象的視覺傳遞、對(duì)應(yīng)用系統(tǒng)的結(jié)合,為客戶提供更好的一站式互聯(lián)網(wǎng)解決方案,攜手廣大客戶,共同發(fā)展進(jìn)步。
?
AOF持久化
AOF持久化方式,即Append Only File,Redis通過記錄執(zhí)行修改操作命令這種記小本本的方式進(jìn)行內(nèi)存數(shù)據(jù)持久化。當(dāng)需要通過AOF日志進(jìn)行恢復(fù)數(shù)據(jù)時(shí),Redis服務(wù)端啟動(dòng)后可以從日志文件中回放執(zhí)行命令來實(shí)現(xiàn)內(nèi)存數(shù)據(jù)恢復(fù)。當(dāng)然了,AOF日志中記錄的都是修改的命令,查詢命令不會(huì)修改數(shù)據(jù)所以不需要進(jìn)行記錄。
可能大家都比較熟悉WAL(Write Ahead Log),即日志預(yù)寫機(jī)制,它是數(shù)據(jù)庫非常常用的確保數(shù)據(jù)操作原子性以及持久性的技術(shù)手段。拿Mysql舉栗子,Mysql的WAL體現(xiàn)在undo log以及redo log等這些日志文件中,數(shù)據(jù)庫在執(zhí)行修改操作的時(shí)候并不是立刻將數(shù)據(jù)更新到磁盤上,而是先記錄在日志中,主要目的是如果出現(xiàn)異常,可以直接從redo log中進(jìn)行數(shù)據(jù)恢復(fù),也就是說讓Mysql知道上次意外發(fā)生的時(shí)候操作到底有沒有成功,另外還可以將Mysql的隨機(jī)寫轉(zhuǎn)換為順序?qū)懀嵘齀O性能。但是AOF卻不同,它是在Redis將數(shù)據(jù)寫入內(nèi)存之后,再將相關(guān)的操作命令寫入AOF文件中。
那么問題來了,為什么Redis要采取這種獨(dú)特的數(shù)據(jù)記錄方式,而不是業(yè)界常用的WAL的方式呢?其實(shí)可以從以下兩個(gè)層面思考原因。
(1)AOF文件中保存了執(zhí)行緩存的命令,以便于保證在需要恢復(fù)數(shù)據(jù)的時(shí)候可以進(jìn)行命令重放恢復(fù)數(shù)據(jù),因此需要保證執(zhí)行命令的合法性,而通過先緩存數(shù)據(jù)再進(jìn)行命令追加日志的方式可以確保追加到AOF文件中的的命令都是合法有效的,redis在恢復(fù)數(shù)據(jù)的時(shí)候不需要再去檢查命令是否有效,進(jìn)一步提升內(nèi)存數(shù)據(jù)恢復(fù)的效率。
(2)另外由于是在修改操作命令之后進(jìn)行日志記錄,日志記錄的時(shí)候需要進(jìn)行磁盤IO操作,因此不會(huì)阻塞當(dāng)前的修改命令。
AOF文件內(nèi)容是什么?
在搞清楚Redis為什么采用AOF文件記錄修改命令之后,我們?cè)賮砜纯碅OF文件中到底包含了哪些內(nèi)容。
redis> SET mufeng handsome
OK
Redis客戶端與服務(wù)端之間采用RESP協(xié)議進(jìn)行通信,它是一種應(yīng)用層協(xié)議,對(duì)于Redis這種以效率為追求目標(biāo)的中間件,通信協(xié)議必定要簡(jiǎn)單高效。就上面一條緩存操作命令來說:set mufeng handsome 對(duì)應(yīng)的RESP報(bào)文就是*3$3set$6mufeng$8handsome,為了方便查看進(jìn)行了手動(dòng)換行。
我們來拆解下報(bào)文中各個(gè)屬性的含義,“*3”代表本次操作命令將由三個(gè)分布組成,每一部分都是通過"$數(shù)字"的形式作為起始,后面為對(duì)應(yīng)的命令、鍵或者值。如此處的"$6"就表示后面的命令是一個(gè)6個(gè)字節(jié)的鍵值。所以,appenonly.aof文件中實(shí)際保存的就是這種格式的內(nèi)容。
AOF有沒有丟數(shù)據(jù)的風(fēng)險(xiǎn)?
上文說到Redis通過AOF文件實(shí)現(xiàn)內(nèi)存數(shù)據(jù)持久化,那么是不是就代表緩存數(shù)據(jù)保存就萬無一失了?這樣的持久化方式還有沒有數(shù)據(jù)丟失的風(fēng)險(xiǎn)呢?大家可以設(shè)想一下假設(shè)在操作完Redis之后,還沒來得及將命令寫入AOF文件就宕機(jī)了,那么這個(gè)操作命令就會(huì)丟失,對(duì)應(yīng)的緩存數(shù)據(jù)最新值也會(huì)丟失。因?yàn)榧幢沐礄C(jī)異?;謴?fù)之后,也沒辦法從AOF文件中執(zhí)行丟失的操作命令了。因此,寫入AOF緩沖區(qū)的數(shù)據(jù)什么時(shí)候進(jìn)行持久化落盤,直接決定著AOF持久化方式緩存數(shù)據(jù)丟失的風(fēng)險(xiǎn)大小。
三種AOF落盤策略
針對(duì)AOF緩存中的數(shù)據(jù)在什么時(shí)機(jī)寫入磁盤,Redis提供了三種AOF日志寫入策略供用戶進(jìn)行選擇,通過后臺(tái)線程執(zhí)行不同時(shí)機(jī)的AOF文件數(shù)據(jù)同步操作,在redis.conf配置文件中的配置項(xiàng)appendfsync可以進(jìn)行配置。
【appendfsync:no】?
Redis不用管AOF緩沖區(qū)的數(shù)據(jù)什么時(shí)候?qū)懭氪疟P,將AOF緩沖區(qū)同步數(shù)據(jù)的操作權(quán)交給操作系統(tǒng),操作系統(tǒng)決定什么時(shí)候?qū)⒕彌_區(qū)的數(shù)據(jù)寫入磁盤中。
【appendfsync:everysec】
當(dāng)Redis將數(shù)據(jù)寫入AOF緩沖區(qū)后,每隔1s將緩沖區(qū)的數(shù)據(jù)進(jìn)行磁盤寫入。
【appendfsync:always】?
每執(zhí)行一個(gè)修改命令,都需要將修改的命令進(jìn)行落盤操作。
雖然Redis提供了這三種AOF日志落盤策略供用戶進(jìn)行選擇,但是這三種策略實(shí)際上各有優(yōu)缺點(diǎn)。
【appendfsync:no】
如果設(shè)置了由操作系統(tǒng)進(jìn)行AOF緩沖區(qū)數(shù)據(jù)寫入,那么就相當(dāng)于寫數(shù)據(jù)的時(shí)機(jī)完全交由操作系統(tǒng)來決定,此時(shí)redis對(duì)于緩沖區(qū)數(shù)據(jù)并不可以控制。
【appendfsync:everysec】
如果設(shè)置成每隔一秒進(jìn)行緩存數(shù)據(jù)寫入,雖然不會(huì)像同步寫入那樣存在一定的性能消耗,但是由于存在一秒的時(shí)間間隔,如果在此期間出現(xiàn)服務(wù)器宕機(jī),那么就會(huì)損失這一秒的緩存數(shù)據(jù)。
【appendfsync:always】
雖然可以基本實(shí)現(xiàn)數(shù)據(jù)不丟失,但是由于每次進(jìn)行內(nèi)存數(shù)據(jù)修改都要進(jìn)行落盤操作,因此在一定程度上會(huì)影響主線程性能。
具體采取怎樣的配置策略還是要根據(jù)實(shí)際的業(yè)務(wù)場(chǎng)景來決定,一般推薦使用第二種配置策略【appendfsync:everysec】,在可靠性以及性能方面相對(duì)平衡一點(diǎn)。
AOF文件會(huì)越來越大嗎?
在了解了AOF日志磁盤寫入時(shí)機(jī)之后,我們繼續(xù)來思考下一個(gè)問題。無論采取什么樣的同步數(shù)據(jù)策略,最終都是要將修改命令寫入AOF文件中,因此隨著時(shí)間的推移,這個(gè)文件必定會(huì)越來越大。那么如果文件變得很大之后,無論是文件數(shù)據(jù)新寫入還是Redis通過AOF文件進(jìn)行數(shù)據(jù)恢復(fù),大文件的操作都會(huì)造成IO性能損耗。假如你是Redis的設(shè)計(jì)者,如果遇到這種情況你會(huì)怎么進(jìn)行設(shè)計(jì)優(yōu)化呢?我想無非有兩個(gè)優(yōu)化思路,一個(gè)是化整為零,一個(gè)是想辦法縮小大文件。
化整為零
當(dāng)單個(gè)文件過大時(shí),我們很容易想到的優(yōu)化方法就是將這個(gè)大文件拆分為若干個(gè)小文件。這就好比系統(tǒng)中一旦出現(xiàn)過千萬數(shù)據(jù)庫表的時(shí)候,我們就要結(jié)合實(shí)際的業(yè)務(wù)場(chǎng)景考慮要不要進(jìn)行分庫分表了。所以如果單個(gè)AOF文件太大,那么是不是可以考慮將其按照固定大小進(jìn)行拆分,這樣可以避免單個(gè)AOF文件過大的問題。那么Redis小于7.0版本為什么沒有采用這種方案呢?主要是這種方案并不符合Redis追求簡(jiǎn)單高效的設(shè)計(jì)思想。假設(shè)采用了這種數(shù)據(jù)分塊的方式,那必定需要實(shí)現(xiàn)文件大小檢測(cè)、文件創(chuàng)建、文件索引維護(hù)等等一系列技術(shù)細(xì)節(jié)問題,對(duì)于低版本的Redis來說這些都太繁瑣了,還不如一個(gè)AOF文件來的爽快。
PS:在最新的Redis 7.0版本中,Redis已經(jīng)支持多AOF文件分片機(jī)制,原始的單個(gè)AOF文件會(huì)被拆分為一個(gè)基礎(chǔ)文件以及多個(gè)增量文件。新版本中之所以開始支持多文件存儲(chǔ),我想也是隨著業(yè)務(wù)發(fā)展內(nèi)存數(shù)據(jù)可能會(huì)很龐大,Redis設(shè)計(jì)者發(fā)現(xiàn)如果還是使用單文件存儲(chǔ),大AOF文件操作以及數(shù)據(jù)恢復(fù)都是一個(gè)挑戰(zhàn)。
AOF重寫
既然進(jìn)行文件切割太繁瑣了,那么就單個(gè)AOF文件來說怎么才能減小文件大小呢?那就要從AOF文件的記錄內(nèi)容入手,通過上文我們了解到AOF文件中實(shí)際存儲(chǔ)了修改內(nèi)存數(shù)據(jù)的操作命令,因此我們?cè)诜治鐾赀@些操作命令之后發(fā)現(xiàn),當(dāng)多條命令操作同一個(gè)key的時(shí)候,實(shí)際我們需要的是最新的一條操作命令,除此之外的歷史操作命令我們并不需要關(guān)心。比如【set mufeng handsome】、【set mufeng cool】,如果先后執(zhí)行了這兩個(gè)命令,那么在最終恢復(fù)數(shù)據(jù)的時(shí)候,只要恢復(fù)【set mufeng cool】即可。因此AOF重寫的本質(zhì)就是合并命令,也就是說將多條對(duì)同一key進(jìn)行操作的命令進(jìn)行合并,實(shí)際就是使用最新的key值操作命令來代替之前所有關(guān)于這個(gè)key值的命令。
Redis通過fork子進(jìn)程來完成AOF文件重寫,因此在講AOF重寫過程之前,我們需要先了解下什么是fork子進(jìn)程的原理,這樣更加有利于我們后面了解AOF文件重寫的過程。
什么是fork?
fork函數(shù)是linux內(nèi)核提供給用戶創(chuàng)建進(jìn)程的API,應(yīng)用程序通過調(diào)用fork函數(shù)創(chuàng)建子進(jìn)程,這個(gè)子進(jìn)程可以和原來父進(jìn)程干同樣的事情,也可以和原來主進(jìn)程干不同的事情,這主要取決于對(duì)應(yīng)的參數(shù)。這個(gè)過程就好比孫悟空拔了一根自己的猴毛變出來一個(gè)和自己一模一樣的孫悟空。
因此在fork子進(jìn)程的過程之中,子進(jìn)程復(fù)制了父進(jìn)程的代碼段、數(shù)據(jù)段、堆棧、頁表等,同時(shí)子進(jìn)程擁有獨(dú)立的虛擬內(nèi)存空間(當(dāng)然是從父進(jìn)程那里復(fù)制過來的)。如下所示,實(shí)際上fork()最終調(diào)用的是內(nèi)核copy_process方法復(fù)制進(jìn)程。?
父進(jìn)程fork子進(jìn)程的時(shí)候,子進(jìn)程擁有獨(dú)立的虛擬內(nèi)存空間,那么對(duì)應(yīng)的物理內(nèi)存空間是不是也是獨(dú)立的呢?我們都知道在計(jì)算機(jī)中,內(nèi)存屬于非常寶貴的系統(tǒng)資源,所以大佬們?cè)谠O(shè)計(jì)的時(shí)候都盡可能的減少內(nèi)存空間占用從而提高系統(tǒng)資源利用率。fork子進(jìn)程過程中用到的Copy-On-Write就是典型的內(nèi)存資源管理優(yōu)化機(jī)制,如果子進(jìn)程只是讀取數(shù)據(jù)不進(jìn)行任何的數(shù)據(jù)寫入,那么就和父進(jìn)程公用內(nèi)存空間。當(dāng)子進(jìn)程需要進(jìn)行數(shù)據(jù)寫入的時(shí)候,發(fā)現(xiàn)沒有內(nèi)控空間可以寫入,此時(shí)會(huì)觸發(fā)一個(gè)系統(tǒng)中斷來分配內(nèi)存空間給子進(jìn)程進(jìn)行數(shù)據(jù)寫入。
什么時(shí)機(jī)觸發(fā)AOF重寫?
執(zhí)行bgrewriteaof 命令
當(dāng)我們?cè)诳蛻舳耸謩?dòng)執(zhí)行bgrewriteaof 命令后,可以觸發(fā)AOF文件進(jìn)行重寫,對(duì)應(yīng)Redis源碼中進(jìn)行重寫的bgrewriteaofCommand 函數(shù)會(huì)檢測(cè)檢測(cè)是否滿足進(jìn)行重寫的條件,主要檢測(cè)以下兩個(gè)條件:
【Condition1】:檢測(cè)當(dāng)前是否存在已經(jīng)在執(zhí)行的AOF重寫子進(jìn)程,如果存在的話Redis將不再執(zhí)行AOF文件重寫。
【Condition2】:檢測(cè)當(dāng)前是否存在已經(jīng)在創(chuàng)建RDB文件的子進(jìn)程,如果存在的話Redis將AOF文件重寫任務(wù)置為待調(diào)度狀態(tài),后續(xù)如果滿足了重寫條件,則繼續(xù)執(zhí)行AOF文件重寫任務(wù)。
也就是說,Redis檢測(cè)到當(dāng)前既沒有AOF重寫子進(jìn)程也沒有RDB文件創(chuàng)建子進(jìn)程,那么就可以進(jìn)行AOF文件重寫。對(duì)應(yīng)源碼如下:
//of_child_pid(aof rewrite進(jìn)程pid)、rdb_child_pid(rdb dump進(jìn)程pid)
void bgrewriteaofCommand(redisClient *c){
if (server.aof_child_pid != -1) {
//如果正在aof rewrite,返回錯(cuò)誤信息
addReplyError(c,"Background append only file rewriting already in progress");
} else if (server.rdb_child_pid != -1) {
//如果正在rdb dump,為了避免磁盤壓力,將aof重寫計(jì)劃狀態(tài)置為1,后期再進(jìn)行rewrite;
server.aof_rewrite_scheduled = 1;
addReplyStatus(c,"Background append only file rewriting scheduled");
}
//如果當(dāng)前沒有aof rewrite和rdb dump在進(jìn)行,則調(diào)用rewriteAppendOnlyFileBackground開始aof rewrite。
else if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
addReplyStatus(c,"Background append only file rewriting started");
} else {
//出現(xiàn)異常返回錯(cuò)誤。
addReply(c,shared.err);
}
}
超出配置閾值
如果Redis實(shí)例開啟了AOF配置,同時(shí)配置了auto-aof-rewrite-percentage以及auto-aof-rewrite-min-size,如果超出了閾值會(huì)觸發(fā)AOF重寫。
//沒有rdb子進(jìn)程、沒有aof重寫子進(jìn)程、aof文件設(shè)定了閾值以及aof文件大小絕對(duì)值超過閾值
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;
//超過閾值則進(jìn)行重寫
if (growth >= server.aof_rewrite_perc) {
serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
rewriteAppendOnlyFileBackground();
}
}
aof_rewrite_scheduled被設(shè)置為待調(diào)度狀態(tài)
在bgrewriteaofCommand函數(shù)中,如果當(dāng)前正在執(zhí)行RDB dump操作,那么對(duì)應(yīng)的aof待調(diào)度aof_rewrite_scheduled狀態(tài)就會(huì)被置為1,當(dāng)前RDB dump完成之后,會(huì)繼續(xù)執(zhí)行AOF重寫操作。?
AOF重寫過程是怎樣的?
通過上文的描述,我們知道了Redis觸發(fā)AOF重寫的時(shí)機(jī),那么當(dāng)觸發(fā)重寫之后的具體業(yè)務(wù)是怎樣的呢?我們一起看下AOF重寫的大致流程:
(1)Redis主進(jìn)程首先檢查是不是存在rdb dump進(jìn)程或者aof重寫進(jìn)程正在運(yùn)行,如果不存在Redis主進(jìn)程fork子進(jìn)程進(jìn)行aof文件重寫;
(2)fork出來的子進(jìn)程和原來的Redis主進(jìn)程擁有同樣的內(nèi)存數(shù)據(jù),子進(jìn)程遍歷此時(shí)的內(nèi)存數(shù)據(jù)同時(shí)將內(nèi)存數(shù)據(jù)寫入到臨時(shí)的AOF文件中;
(3)主進(jìn)程此時(shí)仍然可以接收客戶端請(qǐng)求,同時(shí)將新的緩存操作寫入aof_buf以及aof_rewrite_buf中,根據(jù)對(duì)應(yīng)的同步策略,將buf中的數(shù)據(jù)分別寫入舊AOF文件以及臨時(shí)AOF文件中;
(4)重寫完成之后,臨時(shí)AOF文件將替換原有的老的AOF文件,從而完成整個(gè)AOF重寫。
AOF模式優(yōu)點(diǎn)
1、AOF的持久化策略更加豐富些,可以根據(jù)實(shí)際業(yè)務(wù)需要進(jìn)行配置,因此相對(duì)來說在數(shù)據(jù)可靠性方面要更加有優(yōu)勢(shì)一點(diǎn)。
2、AOF文件內(nèi)容比較好理解,更加方便理解業(yè)務(wù)緩存數(shù)據(jù)。
AOF模式缺點(diǎn)
1、通常情況下,同樣的緩存數(shù)據(jù),AOF文件比RDB文件大小要大一些。
2、在文件恢復(fù)場(chǎng)景下,AOF要比DRB恢復(fù)數(shù)據(jù)慢一些。
RDB持久化
RDB(Redis Data Base),所謂的Redis內(nèi)存數(shù)據(jù)快照就是某一時(shí)刻Redis存于內(nèi)存中的所有緩存數(shù)據(jù),這就好比用手機(jī)相機(jī)拍照,記錄當(dāng)時(shí)的美好畫面。Redis可以實(shí)現(xiàn)在固定時(shí)間間隔后將內(nèi)存中的緩存數(shù)據(jù)持久化保存起來。這樣即便是服務(wù)器宕機(jī)或者重啟了,只要RDB快照文件還存在,快照文件中對(duì)應(yīng)的緩存數(shù)據(jù)就不會(huì)丟失,Redis重新啟動(dòng)后會(huì)重新加載RDB文件到內(nèi)存中,快速恢復(fù)緩存數(shù)據(jù),通過這樣的方式保障了緩存數(shù)據(jù)的可靠性。
RDB文件生成過程
我們以bgsave為例子來看下Redis生成RDB文件的大致過程是怎樣的。
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi){
pid_t childpid;
long long start;
// 如果已經(jīng)存在aof重寫子進(jìn)程以及rdb生成子進(jìn)程則直接返回錯(cuò)誤
if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
...
// fork子進(jìn)程進(jìn)行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;
// 通知父進(jìn)程RDB文件生成完畢
sendChildInfo(CHILD_INFO_TYPE_RDB);
}
//子進(jìn)程退出
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
//父進(jìn)程業(yè)務(wù)邏輯
...
}
return C_OK;
}(1)Redis主進(jìn)程首先判斷當(dāng)前是否存在已經(jīng)在執(zhí)行的aof重寫子進(jìn)程以及rdb文件生成子進(jìn)程,如果存在的話則直接進(jìn)行返回。為什么要進(jìn)行這樣的判斷呢?主要還是從服務(wù)器性能方面進(jìn)行考量,如果服務(wù)器有多個(gè)子線程在進(jìn)行RDB持久化操作,那么必定會(huì)對(duì)磁盤造成比較大的IO壓力,如果服務(wù)器中還部署了其他服務(wù)甚至?xí)绊懫渌?wù)的正常運(yùn)行。
(2)Redis主進(jìn)程fork子進(jìn)程進(jìn)行RDB文件生成操作,在fork的過程中,此時(shí)的Redis主進(jìn)程是阻塞的,不能響應(yīng)客戶端請(qǐng)求,子進(jìn)程fork完成之后可以繼續(xù)響應(yīng)客戶端請(qǐng)求。
(3)fork出來的子進(jìn)程遍歷內(nèi)存數(shù)據(jù)進(jìn)行RDB文件生成操作。
(4)如果此時(shí)客戶端的請(qǐng)求需要修改緩存數(shù)據(jù),那么如上面fork子進(jìn)程的原理,通過COW機(jī)制,操作系統(tǒng)會(huì)開辟新的內(nèi)存空間給Redis主進(jìn)程進(jìn)行新的緩存數(shù)據(jù)寫入。
(5)子進(jìn)程快照數(shù)據(jù)生成完成之后,替換原來老的RDB文件。
RDB觸發(fā)時(shí)機(jī)
Redis主要支持兩種持久化操作來生成RDB文件,分別是save、bsave命令方式手動(dòng)生成以及在配置文件中配置時(shí)間間隔自動(dòng)進(jìn)行RDB文件生成。
手動(dòng)命令觸發(fā)
客戶端連接到redis之后我們可以通過save以及bsave命令進(jìn)行RDB文件的立即創(chuàng)建,兩者的區(qū)別如下:
save:通過主線程觸發(fā),會(huì)阻塞Redis業(yè)務(wù),如果內(nèi)存數(shù)據(jù)比較多的話,會(huì)導(dǎo)致長時(shí)間不能響應(yīng)外部請(qǐng)求;
bsave:客戶端執(zhí)行bsave命令進(jìn)行RDB持久化,Redis主線程會(huì)fork子線程出來進(jìn)行RDB文件持久化操作,這樣避免了主線程的阻塞即便正在持久化操作依然可以響應(yīng)外部數(shù)據(jù)緩存請(qǐng)求。
不過這里值得注意的是,雖然fork子進(jìn)程之后不會(huì)阻塞主進(jìn)程,但是在fork的過程中會(huì)阻塞主進(jìn)程,尤其是在內(nèi)存數(shù)據(jù)比較大的時(shí)候,阻塞主進(jìn)程的時(shí)間會(huì)更長。
配置自動(dòng)觸發(fā)
另外在Redis的配置文件redis.conf中,我們可以配置按照一定的時(shí)間間隔來進(jìn)行RDB持久化操作。如下配置:
save 900 1
save 300 10
save 60 10000?
其他的觸發(fā)RDB文件生成的操作這里不再贅述了,像從節(jié)點(diǎn)執(zhí)行全量數(shù)據(jù)同步的時(shí)候,也會(huì)觸發(fā)主節(jié)點(diǎn)生成RDB文件發(fā)送給從節(jié)點(diǎn)。
RDB有沒有丟數(shù)據(jù)的風(fēng)險(xiǎn)?
大家不妨思考下通過RDB文件進(jìn)行緩存數(shù)據(jù)持久化會(huì)有什么問題?存不存在丟失緩存數(shù)據(jù)的風(fēng)險(xiǎn)?這種方式看上去是個(gè)還不錯(cuò)的持久化解決方案,但是實(shí)際上隱藏著一些丟失緩存數(shù)據(jù)的風(fēng)險(xiǎn)。為什么這么說呢?通過分析RDB文件生成的機(jī)制我們可以發(fā)現(xiàn)有兩個(gè)地方存在緩存數(shù)據(jù)丟失的可能性。
場(chǎng)景1:
由于Redis保存RDB快照文件的策略是按照配置的時(shí)間間隔進(jìn)行持久化保存,也就是每隔一個(gè)時(shí)間間隔Redis就會(huì)保存一個(gè)RDB文件。因此在內(nèi)存數(shù)據(jù)有更新但是RDB保存時(shí)間尚未到來的這段時(shí)間如果存在服務(wù)器宕機(jī)或者服務(wù)器重啟的情況,此時(shí)內(nèi)存的數(shù)據(jù)就會(huì)存在丟失的風(fēng)險(xiǎn),因?yàn)镽edis還沒來得及將數(shù)據(jù)持久化到RDB文件中。
場(chǎng)景1中最大的問題就RDB文件持久化存在時(shí)間間隔,而這個(gè)時(shí)間間隔導(dǎo)致了新增的緩存數(shù)據(jù)存在丟失的風(fēng)險(xiǎn)。那么是不是將時(shí)間間隔降低到最小就可以了,比如一秒鐘,即使在這一秒鐘期間出現(xiàn)異常情況,那緩存數(shù)據(jù)也只是丟掉這一秒鐘的緩存數(shù)據(jù),相對(duì)來說數(shù)據(jù)丟失的情況可控一點(diǎn)。但是問題是如果真的每隔1s就保存一個(gè)RDB文件到服務(wù)器磁盤中,那不論是對(duì)Redis本身還是Redis所在的服務(wù)器磁盤IO都是一種負(fù)擔(dān)。
場(chǎng)景2:
?隨著業(yè)務(wù)的不斷發(fā)展,內(nèi)存中的數(shù)據(jù)必定會(huì)越來越大,因此在fork子進(jìn)程來生成RDB文件的過程中,需要復(fù)制的數(shù)據(jù)會(huì)同樣越來越多,耗費(fèi)的時(shí)間也會(huì)越來越多,進(jìn)而阻塞主進(jìn)程的時(shí)間也會(huì)越來越多。如果出現(xiàn)長時(shí)間阻塞主進(jìn)程的情況,那么Redis實(shí)例必定無法響應(yīng)客戶端的數(shù)據(jù)操作請(qǐng)求,最終導(dǎo)致內(nèi)存數(shù)據(jù)沒有進(jìn)行及時(shí)更新,從而出現(xiàn)丟失緩存數(shù)據(jù)的風(fēng)險(xiǎn)。
RDB模式優(yōu)點(diǎn)
1、相比AOF在恢復(fù)數(shù)據(jù)的時(shí)候需要一條條回放操作命令,通過RDB文件恢復(fù)數(shù)據(jù)效率更高;
2、適合全量備份內(nèi)存數(shù)據(jù)場(chǎng)景。
3、同樣規(guī)模的內(nèi)存數(shù)據(jù),RDB文件數(shù)據(jù)更加緊湊,磁盤空間占用更小。
4、可以根據(jù)不同的時(shí)間間隔保存RDB文件,在恢復(fù)數(shù)據(jù)的時(shí)候可以更加靈活地選擇對(duì)應(yīng)版本數(shù)據(jù)進(jìn)行恢復(fù)。
RDB模式缺點(diǎn)
1、由于RDB數(shù)據(jù)保存存在一定的時(shí)間間隔,因此存在丟失緩存數(shù)據(jù)的風(fēng)險(xiǎn);
2、fork子進(jìn)程進(jìn)行RDB文件生成,由于是一次性生成一個(gè)內(nèi)存快照文件,對(duì)于服務(wù)器磁盤IO以及Redis本身來說都屬于重操作,可能會(huì)對(duì)服務(wù)器的磁盤IO造成壓力。
混合持久化
既然AOF以及RDB持久化都有這樣或者那樣的不足,那么有沒有一種持久化方案可以兼顧二者的優(yōu)點(diǎn)來揚(yáng)長避短呢?從4.0版本開始,Redis支持混合持久化的方式來兼顧效率以及數(shù)據(jù)可靠性。在Redis配置文件redis.conf中配置混合持久化:
aof‐use‐rdb‐preamble yes
如果配置了混合持久化,那么Redis主進(jìn)程在fork子進(jìn)程進(jìn)行持久化操作的時(shí)候,原先的將內(nèi)存數(shù)據(jù)轉(zhuǎn)換為操作命令的過程將替換為使用進(jìn)行AOF重寫時(shí)對(duì)應(yīng)的RDB文件內(nèi)容直接放入到重寫后的臨時(shí)文件中,后面再有新的操作命令,都追加到臨時(shí)aof文件中,重寫完成后使用臨時(shí)aof文件替換舊的文件。
混合持久化模式優(yōu)點(diǎn)
1、同時(shí)擁有RDB以及AOF機(jī)制的優(yōu)點(diǎn),在數(shù)據(jù)可靠性以及數(shù)據(jù)恢復(fù)效率上面達(dá)到了很好的平衡。?
混合持久化模式缺點(diǎn)
1、由于Redis從4.0版本才開始支持混合持久化,如果當(dāng)前平臺(tái)中的Redis版本低于4.0,那么就無法使用這個(gè)持久化機(jī)制,因此兼容性不夠友好;
總結(jié)
本文主要分析了Redis AOF、RDB以及混合持久化的內(nèi)存數(shù)據(jù)持久化的機(jī)制原理,同時(shí)分析了兩種持久化方式的優(yōu)點(diǎn)以及缺點(diǎn)。我想只有理解了中間件的特性機(jī)制原理,知道了特性的長處以及不足我們才能設(shè)計(jì)適合我們平臺(tái)的緩存數(shù)據(jù)持久化策略,從而提升平臺(tái)的穩(wěn)定性。
另外在一些優(yōu)秀中間件的學(xué)習(xí)和使用過程中,我們不能僅僅停留在會(huì)用的層面,更應(yīng)該深入底層領(lǐng)會(huì)其架構(gòu)和實(shí)現(xiàn)機(jī)制的設(shè)計(jì)思路,只有搞明白設(shè)計(jì)思路,時(shí)刻站在設(shè)計(jì)者的角度來看待遇到的問題,那么在我們的實(shí)際工作中,如果遇到類似的問題我們可以借鑒這些優(yōu)秀中間件的解決思路來進(jìn)行問題分析。
名稱欄目:緩存數(shù)據(jù)丟了,原來是Redis持久化沒玩明白
URL標(biāo)題:http://m.fisionsoft.com.cn/article/dphcohp.html


咨詢
建站咨詢
