Redis之RDB,AOF和集群同步講解

1 Redis持久化

Redis數(shù)據(jù)是存儲在內(nèi)存中的,但是我們都知道內(nèi)存的數(shù)據(jù)變化是很快的,也容易發(fā)生丟失,為了保證Redis數(shù)據(jù)不丟失,那就要把數(shù)據(jù)從內(nèi)存存儲到磁盤上,以便在服務(wù)器重啟后還能夠從磁盤中恢復(fù)原有數(shù)據(jù),這就是Redis的數(shù)據(jù)持久化。
Redis數(shù)據(jù)持久化有三種方式:

  • AOF 日志(Append Only File,文件追加方式):記錄所有的操作命令,并以文本的形式追加到文件中。
  • RDB快照(Redis DataBase):將某一個(gè)時(shí)刻的內(nèi)存數(shù)據(jù),以二進(jìn)制的方式寫入磁盤(早期默認(rèn)方式)。
  • 混合持久化方式:Redis 4.0 新增了混合持久化的方式,集成了 RDBAOF 的優(yōu)點(diǎn)

1.1 持久化

redis集群同步:


image.png

1.1.1 持久化流程

既然redis的數(shù)據(jù)可以保存在磁盤上,那么這個(gè)流程是什么樣的呢?
要有下面五個(gè)過程:

  • 客戶端向服務(wù)端發(fā)送寫操作(數(shù)據(jù)在客戶端的內(nèi)存中)
  • 數(shù)據(jù)庫服務(wù)端接收到寫請求的數(shù)據(jù)(數(shù)據(jù)在服務(wù)端的內(nèi)存中)
  • 服務(wù)端調(diào)用write這個(gè)系統(tǒng)調(diào)用,將數(shù)據(jù)往磁盤上寫(數(shù)據(jù)在系統(tǒng)內(nèi)存的緩沖區(qū)中)
  • 操作系統(tǒng)將緩沖區(qū)中的數(shù)據(jù)轉(zhuǎn)移到磁盤控制器上(數(shù)據(jù)在磁盤緩存中)
  • 磁盤控制器將數(shù)據(jù)寫到磁盤的物理介質(zhì)中(數(shù)據(jù)真正落到磁盤上)

1.1.2 數(shù)據(jù)同步機(jī)制

對于多副本模式,Redis和關(guān)系型數(shù)據(jù)庫一樣,提供了主從庫模式來保證數(shù)據(jù)副本的一致性。主從庫之間采用的是讀寫分離的方式,即讀操作可以被主庫/從庫接收,但是寫操作只能先被主庫接收執(zhí)行然后才由主庫同步給從庫。

d733f1fdd141fa568545bf0eb6db04da_841d20f6f83d4500916b9068cb2b79f5.png

那么問題來了,為啥要用讀寫分離實(shí)現(xiàn)?

如果每次的修改請求都發(fā)到不同的實(shí)例上,要保證數(shù)據(jù)的一致性,就需要涉及到加鎖和協(xié)商是否完成修改等操作,這會帶來很大的性能開銷。而如果所有的修改都只在主庫上進(jìn)行,就無需協(xié)調(diào)三個(gè)實(shí)例,只需要同步給從庫,進(jìn)而保持?jǐn)?shù)據(jù)一致性。

1.1.3 Redis主從庫同步流程詳解

假設(shè)現(xiàn)在我們有兩個(gè)實(shí)例,分別是主庫實(shí)例1(172.16.19.3)和實(shí)例2(172.16.19.5),我們的目標(biāo)就是讓實(shí)例2成為實(shí)例1的從庫,并進(jìn)行數(shù)據(jù)同步

1.1.3.1 第一次同步過程

在實(shí)戰(zhàn)中,只需要實(shí)例2的shell中執(zhí)行以下命令就可以將實(shí)例2作為實(shí)例1的從庫,并從實(shí)例1上復(fù)制數(shù)據(jù)

replicaof 172.16.19.3 6369

雖然就這一個(gè)命令真簡單,而在這背后,Redis偷偷摸摸地進(jìn)行了三個(gè)階段的操作,如下圖所示


07c5b75e181bca3a005f35e6f742bc61_740bcfc3b03b4a71844072763be8b30b.jpeg
  • 階段1:從庫向主庫發(fā)送psync命令,表示進(jìn)行數(shù)據(jù)同步。該命令格式如下所示:
    命令格式:psync runID offset# psync ? -1
    其中?代表從庫并不知道主庫的runID,-1表示第一次復(fù)制
    主庫收到psync命令確認(rèn)后,會向從庫發(fā)送一個(gè)FULLERSYNC的響應(yīng)命令,并帶上主庫的runID和目前的復(fù)制進(jìn)度offset。
    命令格式:FULLERSYNC runID offset
    FULLERSYNC代表全量復(fù)制,一般用于第一次數(shù)據(jù)同步
  • 階段2:主庫通過發(fā)送RDB文件給從庫,從庫收到后在本地完成數(shù)據(jù)加載。
    需要注意的點(diǎn):
    • 主庫會首先執(zhí)行一次bgsavebgsave不會阻塞主線程)生成RDB文件,然后才發(fā)送給從庫。
    • 從庫會首先清空已有的數(shù)據(jù),然后再加載RDB數(shù)據(jù),因?yàn)樵谕街翱赡鼙4媪似渌麛?shù)據(jù),不清除的話可能會數(shù)據(jù)不一致。
    • 主庫在同步數(shù)據(jù)給從庫中產(chǎn)生的寫操作會用專門的replication buffer記錄,然后在第三個(gè)階段同步過去。
  • 階段3:主庫將第二階段執(zhí)行過程收到的新的寫操作命令,同步給從庫。
    在實(shí)現(xiàn)中,主庫會將新收到的寫操作放到replication buffer中記錄下來,然后將這些操作修改發(fā)給從庫,從庫收到后再重新執(zhí)行一遍這些操作。

以上三個(gè)階段完成之后,主從庫就算完成了一次數(shù)據(jù)同步。

1.1.3.2 為什么用RDB不用AOF

Redis主庫在向從庫同步數(shù)據(jù)時(shí)使用的RDB文件,那么AOF記錄的操作命令更全,相比RDB丟失的數(shù)據(jù)更少,為什么主庫用RDB不用AOF呢?

  • RDB讀取速度相對較快,從庫可以快速完成RDB的讀取,然后再去消費(fèi)replication buffer的數(shù)據(jù)完成一次同步。而如果使用AOF,其體積大讀取速度慢,且需要更大空間的replication buffer,對于一個(gè)主節(jié)點(diǎn)多個(gè)從節(jié)點(diǎn)來說的話,內(nèi)存的占用就會更大;
  • AOFAppend追加模式,同時(shí)讀寫需要考慮并發(fā)安全問題,并且AOF是文本文件,體積較大,浪費(fèi)網(wǎng)絡(luò)帶寬

1.1.3.3 主從級聯(lián)模式降低主庫壓力

主從庫同步過程中,主庫需要完成兩個(gè)耗時(shí)的操作:生成RDB文件 和 傳輸RDB文件?,F(xiàn)實(shí)場景中,從庫一般都會有多個(gè),如果都要和主庫同步的話,會造成主庫的性能壓力 和 網(wǎng)絡(luò)壓力。

主庫需要fork子進(jìn)程來生成RDB文件,這個(gè)fork操作是會阻塞主線程處理正常請求的,雖然后續(xù)的bgsave過程不會阻塞主線程。
此外,傳輸RDB文件也會占用主庫服務(wù)器的網(wǎng)絡(luò)帶寬。

Redis提供了主從級聯(lián)模式,也就是所謂的主-從-從模式。
主-從-從模式下,新增的從庫可以設(shè)置從 集群中的某一個(gè)從庫 中進(jìn)行數(shù)據(jù)同步,從而避免每次都從主庫進(jìn)行同步,降低主庫的資源消耗,保證系統(tǒng)的穩(wěn)定性。

比如,在實(shí)際中通常會手動選擇一個(gè) 內(nèi)存配置較高的 從庫 來作為同步源,其他新的從庫加入后可以從這個(gè)從庫中同步,建立主從關(guān)系。例如,下面的命令就可以實(shí)現(xiàn)新增從庫和某一個(gè)從庫建立主從關(guān)系:
replicaof 所選從庫ID 6379

具體的示意圖如下圖所示:


ecdc9974a04fed5951ad2b378746a635_6469a66db5534b14942b3e8e4e3b3e7b.jpeg

1.1.3.4 全量同步后的增量同步

主從庫通過FULLERSYNC進(jìn)行全量復(fù)制同步 + 主從級聯(lián)模式分擔(dān)主庫壓力 之后,Redis的主從庫之間就會一直維護(hù)一個(gè)長連接來進(jìn)行增量的命令操作同步,這個(gè)過程又被稱之為基于長連接的命令傳播。
為何選擇長連接?因?yàn)榭梢员苊忸l繁建立連接的開銷。
雖然長連接很方便,但也存在一個(gè)風(fēng)險(xiǎn)點(diǎn):主從庫網(wǎng)絡(luò)斷了或 阻塞了。這個(gè)風(fēng)險(xiǎn)可能導(dǎo)致的問題就是:主從庫之間數(shù)據(jù)無法保持一致,客戶端可能從讀庫讀到過時(shí)的數(shù)據(jù)。
當(dāng)然,Redis早就已經(jīng)為我們想好了解決方案,不過得分為兩個(gè)版本來看:

  • Redis 2.8之前,如果出現(xiàn)了網(wǎng)絡(luò)閃斷,Redis主從庫間會重新進(jìn)行一次全量復(fù)制。當(dāng)然,全量復(fù)制就意味著有很大的開銷。
  • Redis 2.8之后,如果出現(xiàn)了網(wǎng)絡(luò)閃斷,Redis主從庫間會采用增量復(fù)制的方式繼續(xù)同步。可以看到,增量復(fù)制肯定比全量復(fù)制開銷要小得多。主要用于網(wǎng)絡(luò)中斷等情況后的復(fù)制,只將中斷期間 mater 執(zhí)行的寫命令發(fā)送給 slave,與全量復(fù)制相比更加高效。

這里的增量復(fù)制的核心要點(diǎn)就在于Redis引入了repl_backlog_buffer緩沖區(qū),現(xiàn)在我們就來看看這個(gè)repl_backlog_buffer到底是個(gè)啥。
repl_backlog_buffer是一個(gè)環(huán)形緩沖區(qū),主庫會記錄自己寫到的位置,從庫則會記錄自己已經(jīng)讀到的位置。下圖展示了repl_backlog_buffer的示意圖:

c417e9a96fe7f1c8bdfb36eed0d21fd3_31318dd75d6f4a5ab56a4ebe6caa8f33.jpeg

可以看到,正常情況下,主從庫的偏移量基本相等。隨著主庫不斷接收新的寫操作,它在緩沖區(qū)中的寫位置會逐步偏離起始位置。對主庫來說,對應(yīng)的偏移量就是 master_repl_offset,只要主庫的新寫操作越多,這個(gè)值也就越大。同理,從庫在復(fù)制完寫操作命令后,它在緩沖區(qū)中的讀位置也開始逐步偏移起始位置。對從庫來說,其對應(yīng)的已復(fù)制偏移量就是 slave_repl_offset。
當(dāng)網(wǎng)絡(luò)閃斷異常情況下,主庫可能會接收到新的寫操作命令,因此,可以看出,主庫的偏移量master_repl_offset > 從庫的偏移量slave_repl_offset。那么,此時(shí)就只需要把 master_repl_offset 和 slave_repl_offset 之間的命令操作同步給從庫就ok了。

綜上所述,增量同步的過程整體如下:

0e4fcaacd2174fa1c0e9d526ca3ae54f_dfb76d1c4e0843dd8407fcf3792d3ead.jpeg

Redis 7.0 之后,采用了共享緩沖區(qū)的設(shè)計(jì)。因?yàn)椴还苁?code>全量復(fù)制還是增量復(fù)制,當(dāng)寫請求到達(dá) master 時(shí),指令會分別寫入所有 slavereplication buffer 以及 repl_backlog_buffer。重復(fù)保存,太浪費(fèi)內(nèi)存了。
共享緩沖區(qū):既然存儲內(nèi)容是一樣,直接的做法就是主從復(fù)制在命令傳播時(shí),將這些寫命令放在一個(gè)全局的復(fù)制緩沖區(qū)中,多個(gè) slave 共享這份數(shù)據(jù),不同 slave 引用緩沖區(qū)的不同內(nèi)容。

1.1.3.5 repl_backlog_buffer擴(kuò)展

在增量復(fù)制過程中,需要注意的點(diǎn):repl_backlog_buffer是一個(gè)環(huán)形緩沖區(qū),在緩沖區(qū)被寫滿了之后,主庫再次寫入時(shí)就會覆蓋掉之前寫入的操作。
那么問題來了,如果從庫讀取的速度很慢,就有可能出現(xiàn)從庫讀取到了不一致的數(shù)據(jù)。如何解決?
Redis提供了一個(gè) repl_backlog_size 的參數(shù),它與緩沖區(qū)的空間大小緊密相關(guān)。在實(shí)際中,一般其設(shè)置為:repl_backlog_size = 緩沖區(qū)空間大小 * 2,這樣可以降低由于讀庫消費(fèi)速度慢導(dǎo)致的數(shù)據(jù)不一致。
緩沖空間的計(jì)算公式是:

緩沖空間大小 = 主庫寫入命令速度 * 操作大小 - 主從庫間網(wǎng)絡(luò)傳輸命令速度 * 操作大小

但是,如果并發(fā)請求量特別大,兩倍的緩沖區(qū)空間都不夠用,主從庫仍然存在不一致的風(fēng)險(xiǎn),那么此時(shí),可能需要根據(jù)Redis所在服務(wù)器的性能指標(biāo)(主要是內(nèi)存資源)再增加一些 repl_backlog_size 值。

1.2 RDB機(jī)制

RDB其實(shí)就是把數(shù)據(jù)以快照的形式保存在磁盤上。什么是快照呢,你可以理解成把當(dāng)前時(shí)刻的數(shù)據(jù)拍成一張照片保存下來。
RDB持久化是指在指定的時(shí)間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤。也是 默認(rèn)的持久化方式(早期),這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進(jìn)制文件中,默認(rèn)的文件名為dump.rdb
RDB采用的是內(nèi)存快照的方式,它記錄的是某一時(shí)刻的數(shù)據(jù),而不是操作,所以采用RDB方法做故障恢復(fù)時(shí)只需要直接把RDB文件讀入內(nèi)存即可,實(shí)現(xiàn)快速恢復(fù)。

在我們安裝了redis之后,所有的配置都是在redis.conf文件中,里面保存了RDBAOF兩種持久化機(jī)制的各種配置

既然RDB機(jī)制是通過把某個(gè)時(shí)刻的所有數(shù)據(jù)生成一個(gè)快照來保存,那么就應(yīng)該有一種觸發(fā)機(jī)制,是實(shí)現(xiàn)這個(gè)過程。對于RDB來說,提供了三種機(jī)制:savebgsave、自動化。我們分別來看一下

1.2.1 save觸發(fā)方式

該命令會阻塞當(dāng)前Redis服務(wù)器,執(zhí)行save命令期間,Redis不能處理其他命令,直到RDB過程完成為止。具體流程如下:

image.png

執(zhí)行完成時(shí)候如果存在老的RDB文件,就把新的替代掉舊的。我們的客戶端可能都是幾萬或者是幾十萬,這種方式顯然不可取

1.2.2 bgsave

1.2.2.1 bgsave觸發(fā)方式

執(zhí)行該命令時(shí),Redis會在后臺異步進(jìn)行快照操作,快照同時(shí)還可以響應(yīng)客戶端請求。具體流程如下:

image.png

具體操作是Redis進(jìn)程執(zhí)行fork操作創(chuàng)建子進(jìn)程,RDB持久化過程由子進(jìn)程負(fù)責(zé),完成后自動結(jié)束。阻塞只發(fā)生在fork階段,一般時(shí)間很短?;旧?code>Redis內(nèi)部所有的RDB操作都是采用bgsave命令即默認(rèn)配置

在執(zhí)行快照的同時(shí),Redis 會借助操作系統(tǒng)提供的寫時(shí)復(fù)制技術(shù)(Copy-On-Write, COW),正常處理寫操作。bgsave 子進(jìn)程是由主線程 fork 生成的,可以共享主線程的所有內(nèi)存數(shù)據(jù)。bgsave 子進(jìn)程運(yùn)行后,開始讀取主線程的內(nèi)存數(shù)據(jù),并把它們寫入 RDB 文件

Redis是怎么解決在bgsave做快照的時(shí)候允許數(shù)據(jù)修改呢?
這里主要是利用bgsave的子線程實(shí)現(xiàn)的,具體操作如下:
如果主線程執(zhí)行讀操作,則主線程和 bgsave 子進(jìn)程互相不影響;
如果主線程執(zhí)行寫操作,則被修改的數(shù)據(jù)會復(fù)制一份副本,然后,主線程在這個(gè)數(shù)據(jù)副本上進(jìn)行修改。同時(shí),bgsave 子進(jìn)程可以繼續(xù)把原來的數(shù)據(jù) 寫入 RDB 文件,在這個(gè)過程中,主線程仍然可以直接修改原來的數(shù)據(jù)

image.png

雖然 bgsave 執(zhí)行時(shí)不阻塞主線程,但是,如果頻繁地執(zhí)行全量快照,也會帶來兩方面的開銷:

  • 一方面,頻繁將全量數(shù)據(jù)寫入磁盤,會給磁盤帶來很大壓力,多個(gè)快照競爭有限的磁盤帶寬,前一個(gè)快照還沒有做完,后一個(gè)又開始做了,容易造成惡性循環(huán)(所以,在 Redis 中如果有一個(gè) bgsave 在運(yùn)行,就不會再啟動第二個(gè) bgsave 子進(jìn)程)
  • 另一方面,bgsave 子進(jìn)程需要通過 fork 操作從主線程創(chuàng)建出來。雖然,子進(jìn)程在創(chuàng)建后不會再阻塞主線程,但是,fork 這個(gè)創(chuàng)建過程本身會阻塞主線程 ,而且 主線程的內(nèi)存越大,阻塞時(shí)間越長。

需要注意RedisRDB 的執(zhí)行頻率非常重要,因?yàn)檫@會影響快照數(shù)據(jù)的完整性以及 Redis 的穩(wěn)定性,所以在 Redis 4.0 后,增加了 AOFRDB 混合的數(shù)據(jù)持久化機(jī)制: 把數(shù)據(jù)以 RDB 的方式寫入文件,再將后續(xù)的操作命令以 AOF 的格式存入文件,既保證了 Redis 重啟速度,又降低數(shù)據(jù)丟失風(fēng)險(xiǎn),內(nèi)存快照以一定的頻率執(zhí)行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作

1.2.2.2 寫時(shí)復(fù)制

點(diǎn)擊此次了解Linux中寫時(shí)復(fù)制技術(shù)
執(zhí)行 bgsave 命令的時(shí)候,會通過 fork() 創(chuàng)建子進(jìn)程,此時(shí)子進(jìn)程和父進(jìn)程是共享同一片內(nèi)存數(shù)據(jù)的,因?yàn)閯?chuàng)建子進(jìn)程的時(shí)候,會復(fù)制父進(jìn)程的頁表,但是頁表指向的物理內(nèi)存還是一個(gè),此時(shí)如果主線程執(zhí)行讀操作,則主線程和 bgsave 子進(jìn)程互相不影響。

8f8a020dbd534d09970fbcb911023941_6b1e052efb2344f9a94c851b0ae0dd22.png

如果主線程執(zhí)行寫操作,則被修改的數(shù)據(jù)會復(fù)制一份副本,然后 bgsave 子進(jìn)程會把該副本數(shù)據(jù)寫入 RDB 文件,在這個(gè)過程中,主線程仍然可以直接修改原來的數(shù)據(jù)。

b518b4c5cfc0c94127d3a4afc013fca4_05e9e9c4814642d39d2a9896a62356d4.png

1.2.3 自動觸發(fā)

自動觸發(fā)是由我們的配置文件來完成的。在redis.conf配置文件中,里面有如下配置,我們可以去設(shè)置:

  • save:這里是用來配置觸發(fā) RedisRDB 持久化條件,也就是什么時(shí)候?qū)?nèi)存中的數(shù)據(jù)保存到硬盤。比如save m n。表示m秒內(nèi)數(shù)據(jù)集存在n次修改時(shí),自動觸發(fā)bgsave。
    默認(rèn)如下配置:
    表示900 秒內(nèi)如果至少有 1個(gè)key的值變化,則保存save 900 1
    表示300 秒內(nèi)如果至少有 10個(gè)key 的值變化,則保存save 300 10
    表示60 秒內(nèi)如果至少有 10000個(gè)key 的值變化,則保存save 60 10000
    不需要持久化,那么你可以注釋掉所有的 save行來停用保存功能。
  • stop-writes-on-bgsave-error :默認(rèn)值為yes。當(dāng)啟用了RDB且最后一次后臺保存數(shù)據(jù)失敗,Redis是否停止接收數(shù)據(jù)。這會讓用戶意識到數(shù)據(jù)沒有正確持久化到磁盤上,否則沒有人會注意到災(zāi)難(disaster)發(fā)生了。如果Redis重啟了,那么又可以重新開始接收數(shù)據(jù)了
  • rdbcompression :默認(rèn)值是yes。對于存儲到磁盤中的快照,可以設(shè)置是否進(jìn)行壓縮存儲。
  • rdbchecksum :默認(rèn)值是yes。在存儲快照后,我們還可以讓redis使用CRC64算法來進(jìn)行數(shù)據(jù)校驗(yàn),但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關(guān)閉此功能。
  • dbfilename :設(shè)置快照的文件名,默認(rèn)是 dump.rdb
  • dir:設(shè)置快照文件的存放路徑,這個(gè)配置項(xiàng)一定是個(gè)目錄,而不能是文件名

1.2.4 RDB的優(yōu)勢和劣勢

1.2.4.1 優(yōu)勢

RDB文件緊湊,全量備份,非常適合用于進(jìn)行備份和災(zāi)難恢復(fù)。
生成RDB文件的時(shí)候,redis主進(jìn)程會fork()一個(gè)子進(jìn)程來處理所有保存工作,主進(jìn)程不需要進(jìn)行任何磁盤IO操作。
RDB在恢復(fù)大數(shù)據(jù)集時(shí)的速度比 AOF 的恢復(fù)速度要快。

1.2.4.2 劣勢

RDB快照是一次全量備份,存儲的是內(nèi)存數(shù)據(jù)的二進(jìn)制序列化形式,存儲上非常緊湊。當(dāng)進(jìn)行快照持久化時(shí),會開啟一個(gè)子進(jìn)程專門負(fù)責(zé)快照持久化,子進(jìn)程會擁有父進(jìn)程的內(nèi)存數(shù)據(jù),父進(jìn)程修改內(nèi)存子進(jìn)程不會反應(yīng)出來,所以在快照持久化期間修改的數(shù)據(jù)不會被保存,可能丟失數(shù)據(jù)。

需要注意,RedisRDB 的執(zhí)行頻率非常重要,因?yàn)檫@會影響快照數(shù)據(jù)的完整性以及 Redis 的穩(wěn)定性,所以在 Redis 4.0 后,增加了 AOFRDB 混合的數(shù)據(jù)持久化機(jī)制: 把數(shù)據(jù)以 RDB 的方式寫入文件,再將后續(xù)的操作命令以 AOF 的格式存入文件,既保證了 Redis 重啟速度,又降低數(shù)據(jù)丟失風(fēng)險(xiǎn)

1.3 AOF機(jī)制

全量備份總是耗時(shí)的,有時(shí)候我們提供一種更加高效的方式AOF,工作機(jī)制很簡單,redis會將每一個(gè)收到的寫命令都通過write函數(shù)追加到文件中。通俗的理解就是日志記錄

AOF采用的是寫后日志的方式,Redis先執(zhí)行命令把數(shù)據(jù)寫入內(nèi)存,然后再記錄日志到文件中。AOF日志記錄的是操作命令,不是實(shí)際的數(shù)據(jù),如果采用AOF方法做故障恢復(fù)時(shí)需要將全量日志都執(zhí)行一遍

image.png

AOF 里記錄的是 Redis 收到的每一條命令,這些命令是以文本形式保存的。
我們以 Redis 收到set testkey testvalue命令后記錄的日志為例,看看 AOF 日志的內(nèi)容。其中,*3 表示當(dāng)前命令有三個(gè)部分,每部分都是由 $+數(shù)字 開頭,后面緊跟著具體的命令、鍵或值。這里,數(shù)字表示這部分中的命令、鍵或值一共有多少字節(jié)。例如,$3 set 表示這部分有 3 個(gè)字節(jié),也就是set命令。

image.png

1.3.1 寫后日志優(yōu)勢和風(fēng)險(xiǎn)

AOF采用的是寫后日志的方式,我們平時(shí)用的MySQL則采用的是 寫前日志,那 Redis為什么要先執(zhí)行命令,再把數(shù)據(jù)寫入日志呢?
這個(gè)主要是由于Redis在寫入日志之前,不對命令進(jìn)行語法檢查,所以只記錄執(zhí)行成功的命令,避免出現(xiàn)記錄錯(cuò)誤命令的情況,而且在命令執(zhí)行后再寫日志不會阻塞當(dāng)前的寫操作

后寫日志的風(fēng)險(xiǎn):

  • 數(shù)據(jù)可能會丟失:如果 Redis 剛執(zhí)行完命令,此時(shí)發(fā)生故障宕機(jī),會導(dǎo)致這條命令存在丟失的風(fēng)險(xiǎn)
    • 如果此時(shí) Redis 是用作緩存,還可以從后端數(shù)據(jù)庫重新讀入數(shù)據(jù)進(jìn)行恢復(fù)。
    • 如果 Redis 是直接用作數(shù)據(jù)庫的話,此時(shí),因?yàn)槊顩]有記入日志,所以就無法用日志進(jìn)行恢復(fù)了。
  • 可能阻塞其他操作:AOF 日志其實(shí)也是在主線程中執(zhí)行,所以當(dāng) Redis 把日志文件寫入磁盤的時(shí)候,還是會阻塞后續(xù)的操作無法執(zhí)行,(即:AOF 雖然避免了對當(dāng)前命令的阻塞,但可能會給下一個(gè)操作帶來阻塞風(fēng)險(xiǎn))
    AOF 日志也是在主線程中執(zhí)行(寫回策略為 always 時(shí)),如果在把日志文件寫入磁盤時(shí),磁盤寫壓力大,就會導(dǎo)致寫盤很慢,進(jìn)而導(dǎo)致后續(xù)的操作也無法執(zhí)行了。

1.3.2 AOF三種觸發(fā)機(jī)制

  • 每修改同步always:每個(gè)寫命令執(zhí)行完,立馬同步地將日志寫回磁盤,同步持久化,每次發(fā)生數(shù)據(jù)變更會被立即記錄到磁盤,性能較差但數(shù)據(jù)完整性比較好
  • 每秒同步everysec:每個(gè)寫命令執(zhí)行完,只是先把日志寫到 AOF 文件的內(nèi)存緩沖區(qū),每隔一秒把緩沖區(qū)中的內(nèi)容寫入磁盤
    異步操作,每秒記錄,如果一秒內(nèi)宕機(jī),有數(shù)據(jù)丟失,
  • 不同步no:每個(gè)寫命令執(zhí)行完,只是先把日志寫到 AOF 文件的內(nèi)存緩沖區(qū),由操作系統(tǒng)決定何時(shí)將緩沖區(qū)內(nèi)容寫回磁盤
配置項(xiàng) 寫回時(shí)機(jī) 優(yōu)點(diǎn) 缺點(diǎn)
Always 同步寫回 可靠性高,數(shù)據(jù)基本不丟失 每個(gè)寫命令都有落盤,性能影響較大
Everysec 每秒寫回 性能適中 宕機(jī)時(shí)丟失1秒內(nèi)的數(shù)據(jù)
No 操作系統(tǒng)控制的寫回 性能好 宕機(jī)時(shí)丟失數(shù)據(jù)較多

我們就可以根據(jù)系統(tǒng)對高性能和高可靠性的要求,來選擇使用哪種寫回策略了。

  • 想要獲得高性能,就選擇 No 策略;
  • 想要得到高可靠性保證,就選擇 Always 策略;
  • 允許數(shù)據(jù)有一點(diǎn)丟失,又希望性能別受太大影響的話,那么就選擇 Everysec 策略

1.3.3 文件重寫

1.3.3.1 重寫的作用

AOF 是以文件的形式在記錄接收到的所有寫命令。隨著接收的寫命令越來越多,AOF 文件會越來越大。這也就意味著,我們一定要小心 AOF 文件過大帶來的性能問題,主要在于以下三個(gè)方面:

  • 文件系統(tǒng)本身對文件大小有限制,無法保存過大的文件;
  • 如果文件太大,之后再往里面追加命令記錄的話,效率也會變低;
  • 如果發(fā)生宕機(jī),AOF 中記錄的命令要一個(gè)個(gè)被重新執(zhí)行,用于故障恢復(fù),如果日志文件太大,整個(gè)恢復(fù)過程就會非常緩慢,這就會影響到 Redis 的正常使用。

AOF 重寫機(jī)制就是在重寫時(shí),Redis 根據(jù)數(shù)據(jù)庫的現(xiàn)狀創(chuàng)建一個(gè)新的 AOF 文件,也就是說,讀取數(shù)據(jù)庫中的所有鍵值對,然后對每一個(gè)鍵值對用一條命令記錄它的寫入 。重寫機(jī)制具有多變一功能。所謂的多變一,也就是說,舊日志文件中的多條命令,在重寫后的新日志中變成了一條命令。

image.png

1.3.3.2 重寫的過程

AOF 日志由主線程寫回不同,重寫過程是由 后臺子進(jìn)程 bgrewriteaof 來完成的,這也是為了避免阻塞主線程 ,導(dǎo)致數(shù)據(jù)庫性能下降。
可以把重寫的過程總結(jié)為 一個(gè)拷貝,兩處日志

  • 一個(gè)拷貝:就是指,每次執(zhí)行重寫時(shí),主線程 fork 出后臺的 bgrewriteaof 子進(jìn)程。此時(shí),fork 會把主線程的內(nèi)存拷貝一份給 bgrewriteaof 子進(jìn)程,這里面就包含了數(shù)據(jù)庫的最新數(shù)據(jù)。然后,bgrewriteaof 子進(jìn)程就可以在不影響主線程的情況下,逐一把拷貝的數(shù)據(jù)寫成操作,記入重寫日志。
  • 第一處日志:指的是因?yàn)橹骶€程未阻塞,仍然可以處理新來的操作,Redis會把這個(gè)操作寫到它的緩沖區(qū)。這樣一來,即使宕機(jī)了,這個(gè) AOF 日志的操作仍然是齊全的,可以用于恢復(fù)。
  • 第二處日志:就是指新的 AOF 重寫日志。這個(gè)操作也會被寫到重寫日志的緩沖區(qū)。這樣,重寫日志也不會丟失最新的操作。等到拷貝數(shù)據(jù)的所有操作記錄重寫完成后,重寫日志記錄的這些最新操作也會寫入新的 AOF 文件,以保證數(shù)據(jù)庫最新狀態(tài)的記錄。

此時(shí),我們就可以用新的 AOF 文件替代舊文件了。

image.png

為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。將內(nèi)存中的數(shù)據(jù)以命令的方式保存到臨時(shí)文件中,同時(shí)會fork出一條新進(jìn)程來將文件重寫。

image.png

設(shè)置 AOF 重寫策略,例如配置auto-aof-rewrite-percentageauto-aof-rewrite-min-size,讓Redis自動在文件增長到一定比例或超過最小尺寸時(shí)觸發(fā)AOF重寫。

1.3.3.3 總結(jié)

總結(jié)來說,每次 AOF 重寫時(shí),Redis 會先執(zhí)行一個(gè)內(nèi)存拷貝,用于重寫;然后,使用兩個(gè)日志保證在重寫過程中,新寫入的數(shù)據(jù)不會丟失。而且,因?yàn)?Redis 采用子進(jìn)程進(jìn)行日志重寫,所以,這個(gè)過程并不會阻塞主線程 。

正因?yàn)橛涗浀氖遣僮髅?,而不是?shí)際的數(shù)據(jù),所以,用 AOF 方法進(jìn)行故障恢復(fù)的時(shí)候,需要逐一把操作日志都執(zhí)行一遍。如果操作日志非常多,Redis 就會恢復(fù)得很緩慢,影響到正常使用

1.3.4 AOF增量同步

AOF增量同步:配置aof-rewrite-incremental-fsync選項(xiàng),可以控制在AOF重寫過程中增量地進(jìn)行fsync操作,從而減少AOF文件過大時(shí)的同步操作開銷。

這個(gè)選項(xiàng)的作用是控制Redis在執(zhí)行AOF重寫時(shí)對磁盤的同步頻率。默認(rèn)情況下,當(dāng)進(jìn)行AOF重寫時(shí),Redis會在重寫完成后執(zhí)行一次完整的fsync操作來確保AOF文件的持久性,這可能會導(dǎo)致性能下降,特別是對于大型AOF文件而言。啟用 aof-rewrite-incremental-fsync 后,Redis會在重寫AOF文件的過程中增量地進(jìn)行fsync操作,而不是在重寫完成后進(jìn)行一次完整的fsync操作,這樣可以降低磁盤I/O壓力,提高性能。

要啟用 aof-rewrite-incremental-fsync,你需要在Redis配置文件中設(shè)置該選項(xiàng)為 yes:

aof-rewrite-incremental-fsync yes

啟用此選項(xiàng)后,Redis在進(jìn)行AOF重寫時(shí)將采用增量fsync操作,以降低同步操作的開銷。
這樣,當(dāng)AOF文件過大時(shí),Redis就可以更加高效地進(jìn)行AOF重寫,減少同步操作對性能的影響

1.3.5 優(yōu)缺點(diǎn)

1.3.5.1 優(yōu)點(diǎn)

  • AOF可以更好的保護(hù)數(shù)據(jù)不丟失,一般AOF會每隔1秒,通過一個(gè)后臺線程執(zhí)行一次fsync操作,最多丟失1秒鐘的數(shù)據(jù)。
  • AOF日志文件沒有任何磁盤尋址的開銷,寫入性能非常高,文件不容易破損。
  • AOF日志文件即使過大的時(shí)候,出現(xiàn)后臺重寫操作,也不會影響客戶端的讀寫。
  • AOF日志文件的命令通過非??勺x的方式進(jìn)行記錄,這個(gè)特性非常適合做災(zāi)難性的誤刪除的緊急恢復(fù)。比如某人不小心用flushall命令清空了所有數(shù)據(jù),只要這個(gè)時(shí)候后臺rewrite還沒有發(fā)生,那么就可以立即拷貝AOF文件,將最后一條flushall命令給刪了,然后再將該AOF文件放回去,就可以通過恢復(fù)機(jī)制,自動恢復(fù)所有數(shù)據(jù)

1.3.5.2 缺點(diǎn)

  • 對于同一份數(shù)據(jù)來說,AOF日志文件通常比RDB數(shù)據(jù)快照文件更大
  • AOF開啟后,支持的寫QPS會比RDB支持的寫QPS低,因?yàn)?code>AOF一般會配置成每秒fsync一次日志文件,當(dāng)然,每秒一次fsync,性能也還是很高的
  • 以前AOF發(fā)生過bug,就是通過AOF記錄的日志,進(jìn)行數(shù)據(jù)恢復(fù)的時(shí)候,沒有恢復(fù)一模一樣的數(shù)據(jù)出來

1.4 兩者混合

雖然 bgsave 執(zhí)行時(shí)不阻塞主線程,但是,如果頻繁地執(zhí)行全量快照,也會帶來兩方面的開銷。

  • 一方面,頻繁將全量數(shù)據(jù)寫入磁盤,會給磁盤帶來很大壓力,多個(gè)快照競爭有限的磁盤帶寬,前一個(gè)快照還沒有做完,后一個(gè)又開始做了,容易造成惡性循環(huán)(所以,在 Redis 中如果有一個(gè) bgsave 在運(yùn)行,就不會再啟動第二個(gè) bgsave 子進(jìn)程)
  • 另一方面,bgsave 子進(jìn)程需要通過 fork 操作從主線程創(chuàng)建出來。雖然,子進(jìn)程在創(chuàng)建后不會再阻塞主線程,但是,fork 這個(gè)創(chuàng)建過程本身會阻塞主線程,而且 主線程的內(nèi)存越大,阻塞時(shí)間越長。

RDBAOF到底該如何選擇
選擇的話,兩者加一起才更好。因?yàn)閮蓚€(gè)持久化機(jī)制你明白了,剩下的就是看自己的需求了,需求不同選擇的也不一定,但是通常都是結(jié)合使用。有一張圖可供總結(jié):

image.png

需要注意RedisRDB 的執(zhí)行頻率非常重要,因?yàn)檫@會影響快照數(shù)據(jù)的完整性以及 Redis 的穩(wěn)定性,所以在 Redis 4.0 后,增加了 AOFRDB 混合的數(shù)據(jù)持久化機(jī)制,簡單來說:內(nèi)存快照以一定的頻率執(zhí)行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作,這樣一來,快照不用很頻繁地執(zhí)行,這就避免了頻繁 fork 對主線程的影響。而且,AOF 日志也只用記錄兩次快照間的操作,也就是說,不需要記錄所有操作了,因此,就不會出現(xiàn)文件過大的情況了,也可以避免重寫開銷

image.png

附:為了避免 AOF 文件體積膨脹的問題,還有一個(gè) AOF 重寫機(jī)制對文件瘦身。在 7.0 版本還做了優(yōu)化,提出了 Multi-Part AOF 機(jī)制,因?yàn)樵?7.0 之前的版本中 AOF Rewrite 過程中,主進(jìn)程除了把寫指令寫到 AOF 緩沖區(qū)以外,還要寫到 AOF 重寫緩沖區(qū)中。一份數(shù)據(jù)要寫兩個(gè)緩沖區(qū),還要寫到兩個(gè) AOF 文件,產(chǎn)生兩次磁盤 I/O ,太浪費(fèi)了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容