當(dāng)服務(wù)器宕機(jī)后,數(shù)據(jù)全部丟失:我們很容易想到的一個(gè)解決方案是從后端數(shù)據(jù)庫(kù)恢復(fù)這些數(shù)據(jù),但這種方式存在兩個(gè)問(wèn)題:一是,需要頻繁訪問(wèn)數(shù)據(jù)庫(kù),會(huì)給數(shù)據(jù)庫(kù)帶來(lái)巨大的壓力;二是,這些數(shù)據(jù)是從慢速數(shù)據(jù)庫(kù)中讀取出來(lái)的,性能肯定比不上從 Redis 中讀取,導(dǎo)致使用這些數(shù)據(jù)的應(yīng)用程序響應(yīng)變慢。所以,對(duì) Redis 來(lái)說(shuō),實(shí)現(xiàn)數(shù)據(jù)的持久化,避免從后端數(shù)據(jù)庫(kù)中進(jìn)行恢復(fù),是至
關(guān)重要的。
目前,Redis 的持久化主要有兩大機(jī)制,即 AOF 日志和 RDB 快照。
AOF 日志是如何實(shí)現(xiàn)的?
說(shuō)到日志,我們比較熟悉的是數(shù)據(jù)庫(kù)的寫前日志(Write Ahead Log, WAL),也就是
說(shuō),在實(shí)際寫數(shù)據(jù)前,先把修改的數(shù)據(jù)記到日志文件中,以便故障時(shí)進(jìn)行恢復(fù)。不過(guò),
AOF 日志正好相反,它是寫后日志,“寫后”的意思是 Redis 是先執(zhí)行命令,把數(shù)據(jù)寫入
內(nèi)存,然后才記錄日志,如下圖所示:
?那 AOF 為什么要先執(zhí)行命令再記日志呢?要回答這個(gè)問(wèn)題,我們要先知道 AOF 里記錄了
什么內(nèi)容?
傳統(tǒng)數(shù)據(jù)庫(kù)的日志,例如 redo log(重做日志),記錄的是修改后的數(shù)據(jù),而 AOF 里記
錄的是 Redis 收到的每一條命令,這些命令是以文本形式保存的。
我們以 Redis 收到“set testkey testvalue”命令后記錄的日志為例,看看 AOF 日志的內(nèi)
容。其中,“*3”表示當(dāng)前命令有三個(gè)部分,每部分都是由“$+數(shù)字”開頭,后面緊跟著
具體的命令、鍵或值。這里,“數(shù)字”表示這部分中的命令、鍵或值一共有多少字節(jié)。例
如,“$3 set”表示這部分有 3 個(gè)字節(jié),也就是“set”命令。
?????????但是,為了避免額外的檢查開銷,Redis 在向 AOF 里面記錄日志的時(shí)候,并不會(huì)先去對(duì)這
些命令進(jìn)行語(yǔ)法檢查。所以,如果先記日志再執(zhí)行命令的話,日志中就有可能記錄了錯(cuò)誤
的命令,Redis 在使用日志恢復(fù)數(shù)據(jù)時(shí),就可能會(huì)出錯(cuò)。
????????而寫后日志這種方式,就是先讓系統(tǒng)執(zhí)行命令,只有命令能執(zhí)行成功,才會(huì)被記錄到日志
中,否則,系統(tǒng)就會(huì)直接向客戶端報(bào)錯(cuò)。所以,Redis 使用寫后日志這一方式的一大好處
是,可以避免出現(xiàn)記錄錯(cuò)誤命令的情況。
????????除此之外,AOF 還有一個(gè)好處:它是在命令執(zhí)行后才記錄日志,所以不會(huì)阻塞當(dāng)前的寫操
作。
????????不過(guò),AOF 也有兩個(gè)潛在的風(fēng)險(xiǎn) :
????????首先,如果剛執(zhí)行完一個(gè)命令,還沒(méi)有來(lái)得及記日志就宕機(jī)了,那么這個(gè)命令和相應(yīng)的數(shù)
據(jù)就有丟失的風(fēng)險(xiǎn)。如果此時(shí) Redis 是用作緩存,還可以從后端數(shù)據(jù)庫(kù)重新讀入數(shù)據(jù)進(jìn)行恢復(fù),但是,如果 Redis 是直接用作數(shù)據(jù)庫(kù)的話,此時(shí),因?yàn)槊顩](méi)有記入日志,所以就無(wú)法用日志進(jìn)行恢復(fù)了。
????????其次,AOF 雖然避免了對(duì)當(dāng)前命令的阻塞,但可能會(huì)給下一個(gè)操作帶來(lái)阻塞風(fēng)險(xiǎn)。這是因
為,AOF 日志也是在主線程中執(zhí)行的,如果在把日志文件寫入磁盤時(shí),磁盤寫壓力大,就
會(huì)導(dǎo)致寫盤很慢,進(jìn)而導(dǎo)致后續(xù)的操作也無(wú)法執(zhí)行了
????????其次,AOF 雖然避免了對(duì)當(dāng)前命令的阻塞,但可能會(huì)給下一個(gè)操作帶來(lái)阻塞風(fēng)險(xiǎn)。這是因
為,AOF 日志也是在主線程中執(zhí)行的,如果在把日志文件寫入磁盤時(shí),磁盤寫壓力大,就
會(huì)導(dǎo)致寫盤很慢,進(jìn)而導(dǎo)致后續(xù)的操作也無(wú)法執(zhí)行了
三種寫回策略
其實(shí),對(duì)于這個(gè)問(wèn)題,AOF 機(jī)制給我們提供了三個(gè)選擇,也就是 AOF 配置項(xiàng)
appendfsync 的三個(gè)可選值。
????????Always,同步寫回:每個(gè)寫命令執(zhí)行完,立馬同步地將日志寫回磁盤;
????????Everysec,每秒寫回:每個(gè)寫命令執(zhí)行完,只是先把日志寫到 AOF 文件的內(nèi)存緩沖
????????區(qū),每隔一秒把緩沖區(qū)中的內(nèi)容寫入磁盤;
????????No,操作系統(tǒng)控制的寫回:每個(gè)寫命令執(zhí)行完,只是先把日志寫到 AOF 文件的內(nèi)存緩
????????沖區(qū),由操作系統(tǒng)決定何時(shí)將緩沖區(qū)內(nèi)容寫回磁盤
????????針對(duì)避免主線程阻塞和減少數(shù)據(jù)丟失問(wèn)題,這三種寫回策略都無(wú)法做到兩全其美。我們來(lái)
分析下其中的原因:
????????“同步寫回”可以做到基本不丟數(shù)據(jù),但是它在每一個(gè)寫命令后都有一個(gè)慢速的落盤操
作,不可避免地會(huì)影響主線程性能;
????????雖然“操作系統(tǒng)控制的寫回”在寫完緩沖區(qū)后,就可以繼續(xù)執(zhí)行后續(xù)的命令,但是落盤
的時(shí)機(jī)已經(jīng)不在 Redis 手中了,只要 AOF 記錄沒(méi)有寫回磁盤,一旦宕機(jī)對(duì)應(yīng)的數(shù)據(jù)就
丟失了;????????????????
????????每秒寫回”采用一秒寫回一次的頻率,避免了“同步寫回”的性能開銷,雖然減少了
對(duì)系統(tǒng)性能的影響,但是如果發(fā)生宕機(jī),上一秒內(nèi)未落盤的命令操作仍然會(huì)丟失。所
以,這只能算是,在避免影響主線程性能和避免數(shù)據(jù)丟失兩者間取了個(gè)折中。
三種寫回策略的優(yōu)缺點(diǎn):
我們就可以根據(jù)系統(tǒng)對(duì)高性能和高可靠性的要求,來(lái)選擇使用哪種寫回策略
?
????????但是,按照系統(tǒng)的性能需求選定了寫回策略,并不是“高枕無(wú)憂”了。畢竟,AOF 是以文
件的形式在記錄接收到的所有寫命令。隨著接收的寫命令越來(lái)越多,AOF 文件會(huì)越來(lái)越
大。這也就意味著,我們一定要小心 AOF 文件過(guò)大帶來(lái)的性能問(wèn)題。?
????????這里的“性能問(wèn)題”,主要在于以下三個(gè)方面:一是,文件系統(tǒng)本身對(duì)文件大小有限制,
無(wú)法保存過(guò)大的文件;二是,如果文件太大,之后再往里面追加命令記錄的話,效率也會(huì)
變低;三是,如果發(fā)生宕機(jī),AOF 中記錄的命令要一個(gè)個(gè)被重新執(zhí)行,用于故障恢復(fù),如
果日志文件太大,整個(gè)恢復(fù)過(guò)程就會(huì)非常緩慢,這就會(huì)影響到 Redis 的正常使用。
????????所以,我們就要采取一定的控制手段,這個(gè)時(shí)候,AOF 重寫機(jī)制就登場(chǎng)了。
日志文件太大了怎么辦?
????????簡(jiǎn)單來(lái)說(shuō),AOF 重寫機(jī)制就是在重寫時(shí),Redis 根據(jù)數(shù)據(jù)庫(kù)的現(xiàn)狀創(chuàng)建一個(gè)新的 AOF 文
件,也就是說(shuō),讀取數(shù)據(jù)庫(kù)中的所有鍵值對(duì),然后對(duì)每一個(gè)鍵值對(duì)用一條命令記錄它的寫
入。比如說(shuō),當(dāng)讀取了鍵值對(duì)“testkey”: “testvalue”之后,重寫機(jī)制會(huì)記錄 set
testkey testvalue 這條命令。這樣,當(dāng)需要恢復(fù)時(shí),可以重新執(zhí)行該命令,實(shí)
現(xiàn)“testkey”: “testvalue”的寫入。
????????為什么重寫機(jī)制可以把日志文件變小呢? 實(shí)際上,重寫機(jī)制具有“多變一”功能。所謂
的“多變一”,也就是說(shuō),舊日志文件中的多條命令,在重寫后的新日志中變成了一條命
令。?
????????我們知道,AOF 文件是以追加的方式,逐一記錄接收到的寫命令的。當(dāng)一個(gè)鍵值對(duì)被多條
寫命令反復(fù)修改時(shí),AOF 文件會(huì)記錄相應(yīng)的多條命令。但是,在重寫的時(shí)候,是根據(jù)這個(gè)
鍵值對(duì)當(dāng)前的最新?tīng)顟B(tài),為它生成對(duì)應(yīng)的寫入命令。這樣一來(lái),一個(gè)鍵值對(duì)在重寫日志中
只用一條命令就行了,而且,在日志恢復(fù)時(shí),只用執(zhí)行這條命令,就可以直接完成這個(gè)鍵
值對(duì)的寫入了。???????
????????下面這張圖就是一個(gè)例子:
?????????當(dāng)我們對(duì)一個(gè)列表先后做了 6 次修改操作后,列表的最后狀態(tài)是[“D”, “C”, “N”],
此時(shí),只用 LPUSH u:list “N”, “C”, "D"這一條命令就能實(shí)現(xiàn)該數(shù)據(jù)的恢復(fù),這就節(jié)省
了五條命令的空間。對(duì)于被修改過(guò)成百上千次的鍵值對(duì)來(lái)說(shuō),重寫能節(jié)省的空間當(dāng)然就更
大了。
????????不過(guò),雖然 AOF 重寫后,日志文件會(huì)縮小,但是,要把整個(gè)數(shù)據(jù)庫(kù)的最新數(shù)據(jù)的操作日志
都寫回磁盤,仍然是一個(gè)非常耗時(shí)的過(guò)程。這時(shí),我們就要繼續(xù)關(guān)注另一個(gè)問(wèn)題了:重寫
會(huì)不會(huì)阻塞主線程?
AOF 重寫會(huì)阻塞嗎?
????????和 AOF 日志由主線程寫回不同,重寫過(guò)程是由后臺(tái)線程 bgrewriteaof 來(lái)完成的,這也是
為了避免阻塞主線程,導(dǎo)致數(shù)據(jù)庫(kù)性能下降。
????????我把重寫的過(guò)程總結(jié)為“一個(gè)拷貝,兩處日志”
????????一個(gè)拷貝”就是指,每次執(zhí)行重寫時(shí),主線程 fork 出后臺(tái)的 bgrewriteaof 子進(jìn)程。此
時(shí),fork 會(huì)把主線程的內(nèi)存拷貝一份給 bgrewriteaof 子進(jìn)程,這里面就包含了數(shù)據(jù)庫(kù)的
最新數(shù)據(jù)。然后,bgrewriteaof 子進(jìn)程就可以在不影響主線程的情況下,逐一把拷貝的數(shù)
據(jù)寫成操作,記入重寫日志。
????????“兩處日志”又是什么呢?
????????因?yàn)橹骶€程未阻塞,仍然可以處理新來(lái)的操作。此時(shí),如果有寫操作,第一處日志就是指
正在使用的 AOF 日志,Redis 會(huì)把這個(gè)操作寫到它的緩沖區(qū)。這樣一來(lái),即使宕機(jī)了,這
個(gè) AOF 日志的操作仍然是齊全的,可以用于恢復(fù)。
????????而第二處日志,就是指新的 AOF 重寫日志。這個(gè)操作也會(huì)被寫到重寫日志的緩沖區(qū)。這
樣,重寫日志也不會(huì)丟失最新的操作。等到拷貝數(shù)據(jù)的所有操作記錄重寫完成后,重寫日
志記錄的這些最新操作也會(huì)寫入新的 AOF 文件,以保證數(shù)據(jù)庫(kù)最新?tīng)顟B(tài)的記錄。此時(shí),我
們就可以用新的 AOF 文件替代舊文件了。
?總結(jié)來(lái)說(shuō),每次 AOF 重寫時(shí),Redis 會(huì)先執(zhí)行一個(gè)內(nèi)存拷貝,用于重寫;然后,使用兩個(gè)
日志保證在重寫過(guò)程中,新寫入的數(shù)據(jù)不會(huì)丟失。而且,因?yàn)?Redis 采用額外的線程進(jìn)行
數(shù)據(jù)重寫,所以,這個(gè)過(guò)程并不會(huì)阻塞主線程。
問(wèn)題的提出
????????不過(guò),你可能也注意到了,落盤時(shí)機(jī)和重寫機(jī)制都是在“記日志”這一過(guò)程中發(fā)揮作用
的。例如,落盤時(shí)機(jī)的選擇可以避免記日志時(shí)阻塞主線程,重寫可以避免日志文件過(guò)大。
但是,在“用日志”的過(guò)程中,也就是使用 AOF 進(jìn)行故障恢復(fù)時(shí),我們?nèi)匀恍枰阉械?br> 操作記錄都運(yùn)行一遍。再加上 Redis 的單線程設(shè)計(jì),這些命令操作只能一條一條按順序執(zhí)
行,這個(gè)“重放”的過(guò)程就會(huì)很慢了。
????????那么,有沒(méi)有既能避免數(shù)據(jù)丟失,又能更快地恢復(fù)的方法呢?當(dāng)然有,那就是 RDB 快照
了。
????????AOF 日志重寫的時(shí)候,是由 bgrewriteaof 子進(jìn)程來(lái)完成的,不用主線程參與,我們今
天說(shuō)的非阻塞也是指子進(jìn)程的執(zhí)行不阻塞主線程。但是,你覺(jué)得,這個(gè)重寫過(guò)程有沒(méi)有
其他潛在的阻塞風(fēng)險(xiǎn)呢?如果有的話,會(huì)在哪里阻塞?
????????AOF 重寫也有一個(gè)重寫日志,為什么它不共享使用 AOF 本身的日志呢?文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-630073.html
??????? 學(xué)習(xí)就是這樣你會(huì)發(fā)現(xiàn),你學(xué)著學(xué)著就會(huì)發(fā)現(xiàn)很多問(wèn)題文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-630073.html
到了這里,關(guān)于AOF日志:宕機(jī)了,Redis如何避免數(shù)據(jù)丟失的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!