redis主從復制詳述


一、主從復制詳述

原理其實很簡單,master啟動會生成一個run id,首次同步時會發送給slave,slave同步命令會帶上run id以及offset,顯然,slave啟動(初次,重啟)內存中沒有run id,所以
master收到后會全量同步,發生網絡抖動時,slave發生的同步命令會帶上run id以及offset,master就知道去緩存中對應的偏移量開始到結尾那一段的命令發送給slave進行增量
同步。在正常運行過程中,master每收到一個數據變更命令都會發送到所有slave上。
1 全量同步
  Redis全量復制一般發生在Slave初始化階段,這時Slave需要將Master上的所有數據都復制一份。具體步驟如下: 
  1)從服務器連接主服務器,發送SYNC命令; 
  2)主服務器接收到SYNC命名后,開始執行BGSAVE命令生成RDB文件並使用緩沖區記錄此后執行的所有寫命令; 
  3)主服務器BGSAVE執行完后,向 所有從服務器(之所以是向所有從服務器發送應該是因為初始化是幾乎同時收到SYNC命令,歸根到底是向所有發送了SYNC命令的slave發送RDB文件,你可能會想,master會不會重復生成rdb這個動作,答案是不會的,因為bgsave和bgrewriteaof不會同時執行,如果執行bgsave發現有子進程在執行bgsave,則會立即返回)發送快照文件,並在發送期間 繼續用緩存區記錄被執行的寫命令; 
  4)從服務器收到快照文件后丟棄所有舊數據,載入收到的快照; 
  5)主服務器快照發送完畢后開始向從服務器發送緩沖區中的寫命令; 
  6)從服務器完成對快照的載入,開始接收命令請求,並執行來自主服務器緩沖區的寫命令; 
完成上面幾個步驟后就完成了從服務器數據初始化的所有操作,從服務器此時可以接收來自用戶的讀請求。
2 增量同步
  Redis增量復制是指Slave初始化后開始正常工作時主服務器發生的寫操作同步到從服務器的過程,同時記入緩存中,如果slave臨時掉線了,接上來后可以同步這段時間的數據。 
增量復制的過程主要是主服務器每執行一個寫命令就會向從服務器發送相同的寫命令,從服務器接收並執行收到的寫命令。
3 Redis主從同步策略
  主從剛剛連接的時候,進行全量同步;全同步結束后,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis 策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。
4 注意點
如果多個Slave斷線了,需要重啟的時候,因為只要Slave啟動,就會發送sync請求和主機全量同步,當多個同時出現的時候,可能會導致Master IO劇增宕機。
 
---------------------------------------------------------------------------------------------------------------------
以上是根據搜索總結出來的,下面貼出看到的一篇好文:
 

前言

在前面的兩篇文章中,分別介紹了Redis的內存模型Redis的持久化

在Redis的持久化中曾提到,Redis高可用的方案包括持久化、主從復制(及讀寫分離)、哨兵和集群。其中持久化側重解決的是Redis數據的單機備份問題(從內存到硬盤的備份);而主從復制則側重解決數據的多機熱備。此外,主從復制還可以實現負載均衡和故障恢復。

這篇文章中,將詳細介紹Redis主從復制的方方面面,包括:如何使用主從復制、主從復制的原理(重點是全量復制和部分復制、以及心跳機制)、實際應用中需要注意的問題(如數據不一致問題、復制超時問題、復制緩沖區溢出問題)、主從復制相關的配置(重點是repl-timeout、client-output-buffer-limit slave)等。

 

一、主從復制概述

主從復制,是指將一台Redis服務器的數據,復制到其他的Redis服務器。前者稱為主節點(master),后者稱為從節點(slave);數據的復制是單向的,只能由主節點到從節點。

默認情況下,每台Redis服務器都是主節點;且一個主節點可以有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。

主從復制的作用

主從復制的作用主要包括:

  1. 數據冗余:主從復制實現了數據的熱備份,是持久化之外的一種數據冗余方式。
  2. 故障恢復:當主節點出現問題時,可以由從節點提供服務,實現快速的故障恢復;實際上是一種服務的冗余。
  3. 負載均衡:在主從復制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis數據時應用連接主節點,讀Redis數據時應用連接從節點),分擔服務器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis服務器的並發量。
  4. 高可用基石:除了上述作用以外,主從復制還是哨兵和集群能夠實施的基礎,因此說主從復制是Redis高可用的基礎。

二、如何使用主從復制

為了更直觀的理解主從復制,在介紹其內部原理之前,先說明我們需要如何操作才能開啟主從復制。

1. 建立復制

需要注意,主從復制的開啟,完全是在從節點發起的;不需要我們在主節點做任何事情。

從節點開啟主從復制,有3種方式:

(1)配置文件

在從服務器的配置文件中加入:slaveof <masterip> <masterport>

(2)啟動命令

redis-server啟動命令后加入 --slaveof <masterip> <masterport>

(3)客戶端命令

Redis服務器啟動后,直接通過客戶端執行命令:slaveof <masterip> <masterport>,則該Redis實例成為從節點。

上述3種方式是等效的,下面以客戶端命令的方式為例,看一下當執行了slaveof后,Redis主節點和從節點的變化。

2. 實例

准備工作:啟動兩個節點

方便起見,實驗所使用的主從節點是在一台機器上的不同Redis實例,其中主節點監聽6379端口,從節點監聽6380端口;從節點監聽的端口號可以在配置文件中修改:

啟動后可以看到:

兩個Redis節點啟動后(分別稱為6379節點和6380節點),默認都是主節點。

建立復制

此時在6380節點執行slaveof命令,使之變為從節點:

觀察效果

下面驗證一下,在主從復制建立后,主節點的數據會復制到從節點中。

(1)首先在從節點查詢一個不存在的key:

(2)然后在主節點中增加這個key:

(3)此時在從節點中再次查詢這個key,會發現主節點的操作已經同步至從節點:

(4)然后在主節點刪除這個key:

(5)此時在從節點中再次查詢這個key,會發現主節點的操作已經同步至從節點:

3. 斷開復制

通過slaveof <masterip> <masterport>命令建立主從復制關系以后,可以通過slaveof no one斷開。需要注意的是,從節點斷開復制后,不會刪除已有的數據,只是不再接受主節點新的數據變化。

從節點執行slaveof no one后,打印日志如下所示;可以看出斷開復制后,從節點又變回為主節點。

主節點打印日志如下:

三、主從復制的實現原理

上面一節中,介紹了如何操作可以建立主從關系;本小節將介紹主從復制的實現原理。

主從復制過程大體可以分為3個階段:連接建立階段(即准備階段)、數據同步階段、命令傳播階段;下面分別進行介紹。

1. 連接建立階段

該階段的主要作用是在主從節點之間建立連接,為數據同步做好准備。

步驟1:保存主節點信息

從節點服務器內部維護了兩個字段,即masterhost和masterport字段,用於存儲主節點的ip和port信息。

需要注意的是,slaveof是異步命令,從節點完成主節點ip和port的保存后,向發送slaveof命令的客戶端直接返回OK,實際的復制操作在這之后才開始進行。

這個過程中,可以看到從節點打印日志如下:

步驟2:建立socket連接

從節點每秒1次調用復制定時函數replicationCron(),如果發現了有主節點可以連接,便會根據主節點的ip和port,創建socket連接。如果連接成功,則:

從節點:為該socket建立一個專門處理復制工作的文件事件處理器,負責后續的復制工作,如接收RDB文件、接收命令傳播等。

主節點:接收到從節點的socket連接后(即accept之后),為該socket創建相應的客戶端狀態,並將從節點看做是連接到主節點的一個客戶端,后面的步驟會以從節點向主節點發送命令請求的形式來進行。

這個過程中,從節點打印日志如下:

步驟3:發送ping命令

從節點成為主節點的客戶端之后,發送ping命令進行首次請求,目的是:檢查socket連接是否可用,以及主節點當前是否能夠處理請求。

從節點發送ping命令后,可能出現3種情況:

(1)返回pong:說明socket連接正常,且主節點當前可以處理請求,復制過程繼續。

(2)超時:一定時間后從節點仍未收到主節點的回復,說明socket連接不可用,則從節點斷開socket連接,並重連。

(3)返回pong以外的結果:如果主節點返回其他結果,如正在處理超時運行的腳本,說明主節點當前無法處理命令,則從節點斷開socket連接,並重連。

在主節點返回pong情況下,從節點打印日志如下:

步驟4:身份驗證

如果從節點中設置了masterauth選項,則從節點需要向主節點進行身份驗證;沒有設置該選項,則不需要驗證。從節點進行身份驗證是通過向主節點發送auth命令進行的,auth命令的參數即為配置文件中的masterauth的值。

如果主節點設置密碼的狀態,與從節點masterauth的狀態一致(一致是指都存在,且密碼相同,或者都不存在),則身份驗證通過,復制過程繼續;如果不一致,則從節點斷開socket連接,並重連。

步驟5:發送從節點端口信息

身份驗證之后,從節點會向主節點發送其監聽的端口號(前述例子中為6380),主節點將該信息保存到該從節點對應的客戶端的slave_listening_port字段中;該端口信息除了在主節點中執行info Replication時顯示以外,沒有其他作用。

2. 數據同步階段

主從節點之間的連接建立以后,便可以開始進行數據同步,該階段可以理解為從節點數據的初始化。具體執行的方式是:從節點向主節點發送psync命令(Redis2.8以前是sync命令),開始同步。

數據同步階段是主從復制最核心的階段,根據主從節點當前狀態的不同,可以分為全量復制和部分復制,下面會有一章專門講解這兩種復制方式以及psync命令的執行過程,這里不再詳述。

需要注意的是,在數據同步階段之前,從節點是主節點的客戶端,主節點不是從節點的客戶端;而到了這一階段及以后,主從節點互為客戶端。原因在於:在此之前,主節點只需要響應從節點的請求即可,不需要主動發請求,而在數據同步階段和后面的命令傳播階段,主節點需要主動向從節點發送請求(如推送緩沖區中的寫命令),才能完成復制。

3. 命令傳播階段

數據同步階段完成后,主從節點進入命令傳播階段;在這個階段主節點將自己執行的寫命令發送給從節點,從節點接收命令並執行,從而保證主從節點數據的一致性。

在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING和REPLCONF ACK。由於心跳機制的原理涉及部分復制,因此將在介紹了部分復制的相關內容后單獨介紹該心跳機制。

延遲與不一致

需要注意的是,命令傳播是異步的過程,即主節點發送寫命令后並不會等待從節點的回復;因此實際上主從節點之間很難保持實時的一致性,延遲在所難免。數據不一致的程度,與主從節點之間的網絡狀況、主節點寫命令的執行頻率、以及主節點中的repl-disable-tcp-nodelay配置等有關。

repl-disable-tcp-nodelay no:該配置作用於命令傳播階段,控制主節點是否禁止與從節點的TCP_NODELAY;默認no,即不禁止TCP_NODELAY。當設置為yes時,TCP會對包進行合並從而減少帶寬,但是發送的頻率會降低,從節點數據延遲增加,一致性變差;具體發送頻率與Linux內核的配置有關,默認配置為40ms。當設置為no時,TCP會立馬將主節點的數據發送給從節點,帶寬增加但延遲變小。

一般來說,只有當應用對Redis數據不一致的容忍度較高,且主從節點之間網絡狀況不好時,才會設置為yes;多數情況使用默認值no。

四、【數據同步階段】全量復制和部分復制

在Redis2.8以前,從節點向主節點發送sync命令請求同步數據,此時的同步方式是全量復制;在Redis2.8及以后,從節點可以發送psync命令請求同步數據,此時根據主從節點當前狀態的不同,同步方式可能是全量復制或部分復制。后文介紹以Redis2.8及以后版本為例。

  1. 全量復制:用於初次復制或其他無法進行部分復制的情況,將主節點中的所有數據都發送給從節點,是一個非常重型的操作。
  2. 部分復制:用於網絡中斷等情況后的復制,只將中斷期間主節點執行的寫命令發送給從節點,與全量復制相比更加高效。需要注意的是,如果網絡中斷時間過長,導致主節點沒有能夠完整地保存中斷期間執行的寫命令,則無法進行部分復制,仍使用全量復制。

1. 全量復制

Redis通過psync命令進行全量復制的過程如下:

(1)從節點判斷無法進行部分復制,向主節點發送全量復制的請求;或從節點發送部分復制的請求,但主節點判斷無法進行全量復制;具體判斷過程需要在講述了部分復制原理后再介紹。

(2)主節點收到全量復制的命令后,執行bgsave,在后台生成RDB文件,並使用一個緩沖區(稱為復制緩沖區)記錄從現在開始執行的所有寫命令

(3)主節點的bgsave執行完成后,將RDB文件發送給從節點;從節點首先清除自己的舊數據,然后載入接收的RDB文件,將數據庫狀態更新至主節點執行bgsave時的數據庫狀態

(4)主節點將前述復制緩沖區中的所有寫命令發送給從節點,從節點執行這些寫命令,將數據庫狀態更新至主節點的最新狀態

(5)如果從節點開啟了AOF,則會觸發bgrewriteaof的執行,從而保證AOF文件更新至主節點的最新狀態

下面是執行全量復制時,主從節點打印的日志;可以看出日志內容與上述步驟是完全對應的。

主節點的打印日志如下:

從節點打印日志如下圖所示:

其中,有幾點需要注意:從節點接收了來自主節點的89260個字節的數據;從節點在載入主節點的數據之前要先將老數據清除;從節點在同步完數據后,調用了bgrewriteaof。

 

通過全量復制的過程可以看出,全量復制是非常重型的操作:

(1)主節點通過bgsave命令fork子進程進行RDB持久化,該過程是非常消耗CPU、內存(頁表復制)、硬盤IO的;關於bgsave的性能問題,可以參考 深入學習Redis(2):持久化

(2)主節點通過網絡將RDB文件發送給從節點,對主從節點的帶寬都會帶來很大的消耗

(3)從節點清空老數據、載入新RDB文件的過程是阻塞的,無法響應客戶端的命令;如果從節點執行bgrewriteaof,也會帶來額外的消耗

2. 部分復制

由於全量復制在主節點數據量較大時效率太低,因此Redis2.8開始提供部分復制,用於處理網絡中斷時的數據同步。

部分復制的實現,依賴於三個重要的概念:

(1)復制偏移量

主節點和從節點分別維護一個復制偏移量(offset),代表的是主節點向從節點傳遞的字節數;主節點每次向從節點傳播N個字節數據時,主節點的offset增加N;從節點每次收到主節點傳來的N個字節數據時,從節點的offset增加N。

offset用於判斷主從節點的數據庫狀態是否一致:如果二者offset相同,則一致;如果offset不同,則不一致,此時可以根據兩個offset找出從節點缺少的那部分數據。例如,如果主節點的offset是1000,而從節點的offset是500,那么部分復制就需要將offset為501-1000的數據傳遞給從節點。而offset為501-1000的數據存儲的位置,就是下面要介紹的復制積壓緩沖區。

(2)復制積壓緩沖區

復制積壓緩沖區是由主節點維護的、固定長度的、先進先出(FIFO)隊列,默認大小1MB;當主節點開始有從節點時創建,其作用是備份主節點最近發送給從節點的數據。注意,無論主節點有一個還是多個從節點,都只需要一個復制積壓緩沖區。

在命令傳播階段,主節點除了將寫命令發送給從節點,還會發送一份給復制積壓緩沖區,作為寫命令的備份;除了存儲寫命令,復制積壓緩沖區中還存儲了其中的每個字節對應的復制偏移量(offset)。由於復制積壓緩沖區定長且是先進先出,所以它保存的是主節點最近執行的寫命令;時間較早的寫命令會被擠出緩沖區。

由於該緩沖區長度固定且有限,因此可以備份的寫命令也有限,當主從節點offset的差距過大超過緩沖區長度時,將無法執行部分復制,只能執行全量復制。反過來說,為了提高網絡中斷時部分復制執行的概率,可以根據需要增大復制積壓緩沖區的大小(通過配置repl-backlog-size);例如如果網絡中斷的平均時間是60s,而主節點平均每秒產生的寫命令(特定協議格式)所占的字節數為100KB,則復制積壓緩沖區的平均需求為6MB,保險起見,可以設置為12MB,來保證絕大多數斷線情況都可以使用部分復制。

從節點將offset發送給主節點后,主節點根據offset和緩沖區大小決定能否執行部分復制:

  • 如果offset偏移量之后的數據,仍然都在復制積壓緩沖區里,則執行部分復制;
  • 如果offset偏移量之后的數據已不在復制積壓緩沖區中(數據已被擠出),則執行全量復制。

(3)服務器運行ID(runid)

每個Redis節點(無論主從),在啟動時都會自動生成一個隨機ID(每次啟動都不一樣),由40個隨機的十六進制字符組成;runid用來唯一識別一個Redis節點。通過info Server命令,可以查看節點的runid:

主從節點初次復制時,主節點將自己的runid發送給從節點,從節點將這個runid保存起來;當斷線重連時,從節點會將這個runid發送給主節點;主節點根據runid判斷能否進行部分復制:

  • 如果從節點保存的runid與主節點現在的runid相同,說明主從節點之前同步過,主節點會繼續嘗試使用部分復制(到底能不能部分復制還要看offset和復制積壓緩沖區的情況);
  • 如果從節點保存的runid與主節點現在的runid不同,說明從節點在斷線前同步的Redis節點並不是當前的主節點,只能進行全量復制。

3. psync命令的執行

在了解了復制偏移量、復制積壓緩沖區、節點運行id之后,本節將介紹psync命令的參數和返回值,從而說明psync命令執行過程中,主從節點是如何確定使用全量復制還是部分復制的。

psync命令的執行過程可以參見下圖(圖片來源:《Redis設計與實現》):

 

(1)首先,從節點根據當前狀態,決定如何調用psync命令:

  • 如果從節點之前未執行過slaveof或最近執行了slaveof no one,則從節點發送命令為psync ? -1,向主節點請求全量復制;
  • 如果從節點之前執行了slaveof,則發送命令為psync <runid> <offset>,其中runid為上次復制的主節點的runid,offset為上次復制截止時從節點保存的復制偏移量。

(2)主節點根據收到的psync命令,及當前服務器狀態,決定執行全量復制還是部分復制:

  • 如果主節點版本低於Redis2.8,則返回-ERR回復,此時從節點重新發送sync命令執行全量復制;
  • 如果主節點版本夠新,且runid與從節點發送的runid相同,且從節點發送的offset之后的數據在復制積壓緩沖區中都存在,則回復+CONTINUE,表示將進行部分復制,從節點等待主節點發送其缺少的數據即可;
  • 如果主節點版本夠新,但是runid與從節點發送的runid不同,或從節點發送的offset之后的數據已不在復制積壓緩沖區中(在隊列中被擠出了),則回復+FULLRESYNC <runid> <offset>,表示要進行全量復制,其中runid表示主節點當前的runid,offset表示主節點當前的offset,從節點保存這兩個值,以備使用。

4. 部分復制演示

在下面的演示中,網絡中斷幾分鍾后恢復,斷開連接的主從節點進行了部分復制;為了便於模擬網絡中斷,本例中的主從節點在局域網中的兩台機器上。

網絡中斷

網絡中斷一段時間后,主節點和從節點都會發現失去了與對方的連接(關於主從節點對超時的判斷機制,后面會有說明);此后,從節點便開始執行對主節點的重連,由於此時網絡還沒有恢復,重連失敗,從節點會一直嘗試重連。

主節點日志如下:

從節點日志如下:

網絡恢復

網絡恢復后,從節點連接主節點成功,並請求進行部分復制,主節點接收請求后,二者進行部分復制以同步數據。

主節點日志如下:

從節點日志如下:

五、【命令傳播階段】心跳機制

在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING和REPLCONF ACK。心跳機制對於主從復制的超時判斷、數據安全等有作用。

1.主->從:PING

每隔指定的時間,主節點會向從節點發送PING命令,這個PING命令的作用,主要是為了讓從節點進行超時判斷。

PING發送的頻率由repl-ping-slave-period參數控制,單位是秒,默認值是10s。

關於該PING命令究竟是由主節點發給從節點,還是相反,有一些爭議;因為在Redis的官方文檔中,對該參數的注釋中說明是從節點向主節點發送PING命令,如下圖所示:

但是根據該參數的名稱(含有ping-slave),以及代碼實現,我認為該PING命令是主節點發給從節點的。相關代碼如下:

2. 從->主:REPLCONF ACK

在命令傳播階段,從節點會向主節點發送REPLCONF ACK命令,頻率是每秒1次;命令格式為:REPLCONF ACK {offset},其中offset指從節點保存的復制偏移量。REPLCONF ACK命令的作用包括:

(1)實時監測主從節點網絡狀態:該命令會被主節點用於復制超時的判斷。此外,在主節點中使用info Replication,可以看到其從節點的狀態中的lag值,代表的是主節點上次收到該REPLCONF ACK命令的時間間隔,在正常情況下,該值應該是0或1,如下圖所示:

(2)檢測命令丟失:從節點發送了自身的offset,主節點會與自己的offset對比,如果從節點數據缺失(如網絡丟包),主節點會推送缺失的數據(這里也會利用復制積壓緩沖區)。注意,offset和復制積壓緩沖區,不僅可以用於部分復制,也可以用於處理命令丟失等情形;區別在於前者是在斷線重連后進行的,而后者是在主從節點沒有斷線的情況下進行的。

(3)輔助保證從節點的數量和延遲:Redis主節點中使用min-slaves-to-write和min-slaves-max-lag參數,來保證主節點在不安全的情況下不會執行寫命令;所謂不安全,是指從節點數量太少,或延遲過高。例如min-slaves-to-write和min-slaves-max-lag分別是3和10,含義是如果從節點數量小於3個,或所有從節點的延遲值都大於10s,則主節點拒絕執行寫命令。而這里從節點延遲值的獲取,就是通過主節點接收到REPLCONF ACK命令的時間來判斷的,即前面所說的info Replication中的lag值。

六、應用中的問題

1. 讀寫分離及其中的問題

在主從復制基礎上實現的讀寫分離,可以實現Redis的讀負載均衡:由主節點提供寫服務,由一個或多個從節點提供讀服務(多個從節點既可以提高數據冗余程度,也可以最大化讀負載能力);在讀負載較大的應用場景下,可以大大提高Redis服務器的並發量。下面介紹在使用Redis讀寫分離時,需要注意的問題。

(1)延遲與不一致問題

前面已經講到,由於主從復制的命令傳播是異步的,延遲與數據的不一致不可避免。如果應用對數據不一致的接受程度程度較低,可能的優化措施包括:優化主從節點之間的網絡環境(如在同機房部署);監控主從節點延遲(通過offset)判斷,如果從節點延遲過大,通知應用不再通過該從節點讀取數據;使用集群同時擴展寫負載和讀負載等。

在命令傳播階段以外的其他情況下,從節點的數據不一致可能更加嚴重,例如連接在數據同步階段,或從節點失去與主節點的連接時等。從節點的slave-serve-stale-data參數便與此有關:它控制這種情況下從節點的表現;如果為yes(默認值),則從節點仍能夠響應客戶端的命令,如果為no,則從節點只能響應info、slaveof等少數命令。該參數的設置與應用對數據一致性的要求有關;如果對數據一致性要求很高,則應設置為no。

(2)數據過期問題

在單機版Redis中,存在兩種刪除策略:

  • 惰性刪除:服務器不會主動刪除數據,只有當客戶端查詢某個數據時,服務器判斷該數據是否過期,如果過期則刪除。
  • 定期刪除:服務器執行定時任務刪除過期數據,但是考慮到內存和CPU的折中(刪除會釋放內存,但是頻繁的刪除操作對CPU不友好),該刪除的頻率和執行時間都受到了限制。

在主從復制場景下,為了主從節點的數據一致性,從節點不會主動刪除數據,而是由主節點控制從節點中過期數據的刪除。由於主節點的惰性刪除和定期刪除策略,都不能保證主節點及時對過期數據執行刪除操作,因此,當客戶端通過Redis從節點讀取數據時,很容易讀取到已經過期的數據。

Redis 3.2中,從節點在讀取數據時,增加了對數據是否過期的判斷:如果該數據已過期,則不返回給客戶端;將Redis升級到3.2可以解決數據過期問題。

(3)故障切換問題

在沒有使用哨兵的讀寫分離場景下,應用針對讀和寫分別連接不同的Redis節點;當主節點或從節點出現問題而發生更改時,需要及時修改應用程序讀寫Redis數據的連接;連接的切換可以手動進行,或者自己寫監控程序進行切換,但前者響應慢、容易出錯,后者實現復雜,成本都不算低。

(4)總結

在使用讀寫分離之前,可以考慮其他方法增加Redis的讀負載能力:如盡量優化主節點(減少慢查詢、減少持久化等其他情況帶來的阻塞等)提高負載能力;使用Redis集群同時提高讀負載能力和寫負載能力等。如果使用讀寫分離,可以使用哨兵(也可采用redis cluster),使主從節點的故障切換盡可能自動化,並減少對應用程序的侵入。

2. 復制超時問題

主從節點復制超時是導致復制中斷的最重要的原因之一,本小節單獨說明超時問題,下一小節說明其他會導致復制中斷的問題。

超時判斷意義

在復制連接建立過程中及之后,主從節點都有機制判斷連接是否超時,其意義在於:

(1)如果主節點判斷連接超時,其會釋放相應從節點的連接,從而釋放各種資源,否則無效的從節點仍會占用主節點的各種資源(輸出緩沖區、帶寬、連接等);此外連接超時的判斷可以讓主節點更准確的知道當前有效從節點的個數,有助於保證數據安全(配合前面講到的min-slaves-to-write等參數)。

(2)如果從節點判斷連接超時,則可以及時重新建立連接,避免與主節點數據長期的不一致。

判斷機制

主從復制超時判斷的核心,在於repl-timeout參數,該參數規定了超時時間的閾值(默認60s),對於主節點和從節點同時有效;主從節點觸發超時的條件分別如下:

(1)主節點:每秒1次調用復制定時函數replicationCron(),在其中判斷當前時間距離上次收到各個從節點REPLCONF ACK的時間,是否超過了repl-timeout值,如果超過了則釋放相應從節點的連接。

(2)從節點:從節點對超時的判斷同樣是在復制定時函數中判斷,基本邏輯是:

  • 如果當前處於連接建立階段,且距離上次收到主節點的信息的時間已超過repl-timeout,則釋放與主節點的連接;
  • 如果當前處於數據同步階段,且收到主節點的RDB文件的時間超時,則停止數據同步,釋放連接;
  • 如果當前處於命令傳播階段,且距離上次收到主節點的PING命令或數據的時間已超過repl-timeout值,則釋放與主節點的連接。

主從節點判斷連接超時的相關源代碼如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/* Replication cron function, called 1 time per second. */
void  replicationCron( void ) {
     static  long  long  replication_cron_loops = 0;
 
     /* Non blocking connection timeout? */
     if  (server.masterhost &&
         (server.repl_state == REDIS_REPL_CONNECTING ||
          slaveIsInHandshakeState()) &&
          ( time (NULL)-server.repl_transfer_lastio) > server.repl_timeout)
     {
         redisLog(REDIS_WARNING, "Timeout connecting to the MASTER..." );
         undoConnectWithMaster();
     }
 
     /* Bulk transfer I/O timeout? */
     if  (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&
         ( time (NULL)-server.repl_transfer_lastio) > server.repl_timeout)
     {
         redisLog(REDIS_WARNING, "Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value." );
         replicationAbortSyncTransfer();
     }
 
     /* Timed out master when we are an already connected slave? */
     if  (server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&
         ( time (NULL)-server.master->lastinteraction) > server.repl_timeout)
     {
         redisLog(REDIS_WARNING, "MASTER timeout: no data nor PING received..." );
         freeClient(server.master);
     }
 
     //此處省略無關代碼……
 
     /* Disconnect timedout slaves. */
     if  (listLength(server.slaves)) {
         listIter li;
         listNode *ln;
         listRewind(server.slaves,&li);
         while ((ln = listNext(&li))) {
             redisClient *slave = ln->value;
             if  (slave->replstate != REDIS_REPL_ONLINE)  continue ;
             if  (slave->flags & REDIS_PRE_PSYNC)  continue ;
             if  ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
             {
                 redisLog(REDIS_WARNING,  "Disconnecting timedout slave: %s" ,
                     replicationGetSlaveName(slave));
                 freeClient(slave);
             }
         }
     }
 
     //此處省略無關代碼……
 
}

  

需要注意的坑

下面介紹與復制階段連接超時有關的一些實際問題:

(1)數據同步階段:在主從節點進行全量復制bgsave時,主節點需要首先fork子進程將當前數據保存到RDB文件中,然后再將RDB文件通過網絡傳輸到從節點。如果RDB文件過大,主節點在fork子進程+保存RDB文件時耗時過多,可能會導致從節點長時間收不到數據而觸發超時;此時從節點會重連主節點,然后再次全量復制,再次超時,再次重連……這是個悲傷的循環。為了避免這種情況的發生,除了注意Redis單機數據量不要過大,另一方面就是適當增大repl-timeout值,具體的大小可以根據bgsave耗時來調整。

(2)命令傳播階段:如前所述,在該階段主節點會向從節點發送PING命令,頻率由repl-ping-slave-period控制;該參數應明顯小於repl-timeout值(后者至少是前者的幾倍)。否則,如果兩個參數相等或接近,網絡抖動導致個別PING命令丟失,此時恰巧主節點也沒有向從節點發送數據,則從節點很容易判斷超時。

(3)慢查詢導致的阻塞:如果主節點或從節點執行了一些慢查詢(如keys *或者對大數據的hgetall等),導致服務器阻塞;阻塞期間無法響應復制連接中對方節點的請求,可能導致復制超時。

3. 復制中斷問題

主從節點超時是復制中斷的原因之一,除此之外,還有其他情況可能導致復制中斷,其中最主要的是復制緩沖區溢出問題。

復制緩沖區溢出

前面曾提到過,在全量復制階段,主節點會將執行的寫命令放到復制緩沖區中,該緩沖區存放的數據包括了以下幾個時間段內主節點執行的寫命令:bgsave生成RDB文件、RDB文件由主節點發往從節點、從節點清空老數據並載入RDB文件中的數據。當主節點數據量較大,或者主從節點之間網絡延遲較大時,可能導致該緩沖區的大小超過了限制,此時主節點會斷開與從節點之間的連接;這種情況可能引起全量復制->復制緩沖區溢出導致連接中斷->重連->全量復制->復制緩沖區溢出導致連接中斷……的循環。

復制緩沖區的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默認值為client-output-buffer-limit slave 256MB 64MB 60,其含義是:如果buffer大於256MB,或者連續60s大於64MB,則主節點會斷開與該從節點的連接。該參數是可以通過config set命令動態配置的(即不重啟Redis也可以生效)。

當復制緩沖區溢出時,主節點打印日志如下所示:

需要注意的是,復制緩沖區是客戶端輸出緩沖區的一種,主節點會為每一個從節點分別分配復制緩沖區;而復制積壓緩沖區則是一個主節點只有一個,無論它有多少個從節點。

4. 各場景下復制的選擇及優化技巧

在介紹了Redis復制的種種細節之后,現在我們可以來總結一下,在下面常見的場景中,何時使用部分復制,以及需要注意哪些問題。

(1)第一次建立復制

此時全量復制不可避免,但仍有幾點需要注意:如果主節點的數據量較大,應該盡量避開流量的高峰期,避免造成阻塞;如果有多個從節點需要建立對主節點的復制,可以考慮將幾個從節點錯開,避免主節點帶寬占用過大。此外,如果從節點過多,也可以調整主從復制的拓撲結構,由一主多從結構變為樹狀結構(中間的節點既是其主節點的從節點,也是其從節點的主節點);但使用樹狀結構應該謹慎:雖然主節點的直接從節點減少,降低了主節點的負擔,但是多層從節點的延遲增大,數據一致性變差;且結構復雜,維護相當困難。

(2)主節點重啟

主節點重啟可以分為兩種情況來討論,一種是故障導致宕機,另一種則是有計划的重啟。

主節點宕機

主節點宕機重啟后,runid會發生變化,因此不能進行部分復制,只能全量復制。

實際上在主節點宕機的情況下,應進行故障轉移處理,將其中的一個從節點升級為主節點,其他從節點從新的主節點進行復制;且故障轉移應盡量的自動化,后面文章將要介紹的哨兵便可以進行自動的故障轉移。

安全重啟:debug reload

在一些場景下,可能希望對主節點進行重啟,例如主節點內存碎片率過高,或者希望調整一些只能在啟動時調整的參數。如果使用普通的手段重啟主節點,會使得runid發生變化,可能導致不必要的全量復制。

為了解決這個問題,Redis提供了debug reload的重啟方式:重啟后,主節點的runid和offset都不受影響,避免了全量復制。

如下圖所示,debug reload重啟后runid和offset都未受影響:

但debug reload是一柄雙刃劍:它會清空當前內存中的數據,重新從RDB文件中加載,這個過程會導致主節點的阻塞,因此也需要謹慎。

(3)從節點重啟

從節點宕機重啟后,其保存的主節點的runid會丟失,因此即使再次執行slaveof,也無法進行部分復制。

(4)網絡中斷

如果主從節點之間出現網絡問題,造成短時間內網絡中斷,可以分為多種情況討論。

第一種情況:網絡問題時間極為短暫,只造成了短暫的丟包,主從節點都沒有判定超時(未觸發repl-timeout);此時只需要通過REPLCONF ACK來補充丟失的數據即可。

第二種情況:網絡問題時間很長,主從節點判斷超時(觸發了repl-timeout),且丟失的數據過多,超過了復制積壓緩沖區所能存儲的范圍;此時主從節點無法進行部分復制,只能進行全量復制。為了盡可能避免這種情況的發生,應該根據實際情況適當調整復制積壓緩沖區的大小;此外及時發現並修復網絡中斷,也可以減少全量復制。

第三種情況:介於前述兩種情況之間,主從節點判斷超時,且丟失的數據仍然都在復制積壓緩沖區中;此時主從節點可以進行部分復制。

5. 復制相關的配置

這一節總結一下與復制有關的配置,說明這些配置的作用、起作用的階段,以及配置方法等;通過了解這些配置,一方面加深對Redis復制的了解,另一方面掌握這些配置的方法,可以優化Redis的使用,少走坑。

配置大致可以分為主節點相關配置、從節點相關配置以及與主從節點都有關的配置,下面分別說明。

(1)與主從節點都有關的配置

首先介紹最特殊的配置,它決定了該節點是主節點還是從節點:

1)   slaveof <masterip> <masterport>:Redis啟動時起作用;作用是建立復制關系,開啟了該配置的Redis服務器在啟動后成為從節點。該注釋默認注釋掉,即Redis服務器默認都是主節點。

2)   repl-timeout 60:與各個階段主從節點連接超時判斷有關,見前面的介紹。

(2)主節點相關配置

1)   repl-diskless-sync no:作用於全量復制階段,控制主節點是否使用diskless復制(無盤復制)。所謂diskless復制,是指在全量復制時,主節點不再先把數據寫入RDB文件,而是直接寫入slave的socket中,整個過程中不涉及硬盤;diskless復制在磁盤IO很慢而網速很快時更有優勢。需要注意的是,截至Redis3.0,diskless復制處於實驗階段,默認是關閉的。

2)   repl-diskless-sync-delay 5:該配置作用於全量復制階段,當主節點使用diskless復制時,該配置決定主節點向從節點發送之前停頓的時間,單位是秒;只有當diskless復制打開時有效,默認5s。之所以設置停頓時間,是基於以下兩個考慮:(1)向slave的socket的傳輸一旦開始,新連接的slave只能等待當前數據傳輸結束,才能開始新的數據傳輸 (2)多個從節點有較大的概率在短時間內建立主從復制。

3)   client-output-buffer-limit slave 256MB 64MB 60:與全量復制階段主節點的緩沖區大小有關,見前面的介紹。

4)   repl-disable-tcp-nodelay no:與命令傳播階段的延遲有關,見前面的介紹。

5)   masterauth <master-password>:與連接建立階段的身份驗證有關,見前面的介紹。

6)   repl-ping-slave-period 10:與命令傳播階段主從節點的超時判斷有關,見前面的介紹。

7)   repl-backlog-size 1mb:復制積壓緩沖區的大小,見前面的介紹。

8)   repl-backlog-ttl 3600:當主節點沒有從節點時,復制積壓緩沖區保留的時間,這樣當斷開的從節點重新連進來時,可以進行全量復制;默認3600s。如果設置為0,則永遠不會釋放復制積壓緩沖區。

9)   min-slaves-to-write 3與min-slaves-max-lag 10:規定了主節點的最小從節點數目,及對應的最大延遲,見前面的介紹。

(3)從節點相關配置

1)   slave-serve-stale-data yes:與從節點數據陳舊時是否響應客戶端命令有關,見前面的介紹。

2)   slave-read-only yes:從節點是否只讀;默認是只讀的。由於從節點開啟寫操作容易導致主從節點的數據不一致,因此該配置盡量不要修改。

6. 單機內存大小限制

在 深入學習Redis(2):持久化 一文中,講到了fork操作對Redis單機內存大小的限制。實際上在Redis的使用中,限制單機內存大小的因素非常之多,下面總結一下在主從復制中,單機內存過大可能造成的影響:

(1)切主:當主節點宕機時,一種常見的容災策略是將其中一個從節點提升為主節點,並將其他從節點掛載到新的主節點上,此時這些從節點只能進行全量復制;如果Redis單機內存達到10GB,一個從節點的同步時間在幾分鍾的級別;如果從節點較多,恢復的速度會更慢。如果系統的讀負載很高,而這段時間從節點無法提供服務,會對系統造成很大的壓力。

(2)從庫擴容:如果訪問量突然增大,此時希望增加從節點分擔讀負載,如果數據量過大,從節點同步太慢,難以及時應對訪問量的暴增。

(3)緩沖區溢出:(1)和(2)都是從節點可以正常同步的情形(雖然慢),但是如果數據量過大,導致全量復制階段主節點的復制緩沖區溢出,從而導致復制中斷,則主從節點的數據同步會全量復制->復制緩沖區溢出導致復制中斷->重連->全量復制->復制緩沖區溢出導致復制中斷……的循環。

(4)超時:如果數據量過大,全量復制階段主節點fork+保存RDB文件耗時過大,從節點長時間接收不到數據觸發超時,主從節點的數據同步同樣可能陷入全量復制->超時導致復制中斷->重連->全量復制->超時導致復制中斷……的循環。

此外,主節點單機內存除了絕對量不能太大,其占用主機內存的比例也不應過大:最好只使用50%-65%的內存,留下30%-45%的內存用於執行bgsave命令和創建復制緩沖區等。

7. info Replication

在Redis客戶端通過info Replication可以查看與復制相關的狀態,對於了解主從節點的當前狀態,以及解決出現的問題都會有幫助。

主節點:

從節點:

對於從節點,上半部分展示的是其作為從節點的狀態,從connectd_slaves開始,展示的是其作為潛在的主節點的狀態。

info Replication中展示的大部分內容在文章中都已經講述,這里不再詳述。

七、總結

下面回顧一下本文的主要內容:

1、主從復制的作用:宏觀的了解主從復制是為了解決什么樣的問題,即數據冗余、故障恢復、讀負載均衡等。

2、主從復制的操作:即slaveof命令。

3、主從復制的原理:主從復制包括了連接建立階段、數據同步階段、命令傳播階段;其中數據同步階段,有全量復制和部分復制兩種數據同步方式;命令傳播階段,主從節點之間有PING和REPLCONF ACK命令互相進行心跳檢測。

4、應用中的問題:包括讀寫分離的問題(數據不一致問題、數據過期問題、故障切換問題等)、復制超時問題、復制中斷問題等,然后總結了主從復制相關的配置,其中repl-timeout、client-output-buffer-limit slave等對解決Redis主從復制中出現的問題可能會有幫助。

主從復制雖然解決或緩解了數據冗余、故障恢復、讀負載均衡等問題,但其缺陷仍很明顯:故障恢復無法自動化;寫操作無法負載均衡;存儲能力受到單機的限制;這些問題的解決,需要哨兵和集群的幫助,我將在后面的文章中介紹,歡迎關注。

參考文獻

《Redis開發與運維》

《Redis設計與實現》

《Redis實戰》

http://mdba.cn/2015/03/16/redis復制中斷問題-慢查詢/

https://redislabs.com/blog/top-redis-headaches-for-devops-replication-buffer/

http://mdba.cn/2015/03/17/redis主從復制(2)-replication-buffer與replication-backlog/

https://github.com/antirez/redis/issues/918

https://blog.csdn.net/qbw2010/article/details/50496982

https://mp.weixin.qq.com/s?__biz=MzIxMzEzMjM5NQ==&mid=2651029484&idx=1&sn=5882f4c7c390a0a0e4f6dfd872e203b5&chksm=8c4caae8bb3b23fe77909e307d45a071186f55069e5207602c61383eab573885615c1d835904&mpshare=1&scene=1&srcid=0327SokqtxEY3WojWNDMHLYl#rd

10分鍾深入學習Redis的高可用特性“持久化”

編程迷思   數據庫開發   前天

作者:編程迷思

出處:https://www.cnblogs.com/kismetv/p/9137897.html

 

在上一篇文章中,介紹了《Redis的內存模型,從這篇文章開始,將依次介紹 Redis 高可用相關的知識——持久化、復制(及讀寫分離)、哨兵、以及集群。

 

本文將先說明上述幾種技術分別解決了 Redis 高可用的什么問題,然后詳細介紹 Redis 的持久化技術,主要是 RDB 和 AOF 兩種持久化方案。

 

在介紹 RDB 和 AOF 方案時,不僅介紹它的作用及操作方法,同時介紹持久化實現的一些原理細節及需要注意的問題。最后,介紹在實際使用中,持久化方案的選擇,以及經常遇到的問題等。

 

下面分別從以下幾個方面講解:

  • Redis 高可用概述

  • Redis 持久化概述

  • RDB 持久化

  • AOF 持久化

  • 方案選擇與常見問題

  • 總結

 

 

Redis 高可用概述

 

在介紹 Redis 高可用之前,先說明一下在 Redis 的語境中高可用的含義。在 Web 服務器中,高可用是指服務器可以正常訪問的時間,衡量的標准是在多長時間內可以提供正常服務(99.9%、99.99%、99.999% 等等)。

 

但是在 Redis 語境中,高可用的含義似乎要寬泛一些,除了保證提供正常服務(如主從分離、快速容災技術),還需要考慮數據容量的擴展、數據安全不會丟失等。

 

在 Redis 中,實現高可用的技術主要包括持久化、復制、哨兵和集群,下面分別說明它們的作用,以及解決了什么樣的問題:

  • 持久化:持久化是最簡單的高可用方法(有時甚至不被歸為高可用的手段),主要作用是數據備份,即將數據存儲在硬盤,保證數據不會因進程退出而丟失。

  • 復制:復制是高可用 Redis 的基礎,哨兵和集群都是在復制基礎上實現高可用的。復制主要實現了數據的多機備份,以及對於讀操作的負載均衡和簡單的故障恢復。

    缺陷:故障恢復無法自動化,寫操作無法負載均衡,存儲能力受到單機的限制。

  • 哨兵:在復制的基礎上,哨兵實現了自動化的故障恢復。

    缺陷:寫操作無法負載均衡;存儲能力受到單機的限制。

  • 集群:通過集群,Redis 解決了寫操作無法負載均衡,以及存儲能力受到單機限制的問題,實現了較為完善的高可用方案。

 

 

Redis 持久化概述

 

持久化的功能:Redis 是內存數據庫,數據都是存儲在內存中。

 

為了避免進程退出導致數據的永久丟失,需要定期將 Redis 中的數據以某種形式(數據或命令)從內存保存到硬盤;當下次 Redis 重啟時,利用持久化文件實現數據恢復。

 

除此之外,為了進行災難備份,可以將持久化文件拷貝到一個遠程位置。

 

Redis 持久化分為 RDB 持久化和 AOF 持久化:

  • 前者將當前數據保存到硬盤

  • 后者則是將每次執行的寫命令保存到硬盤(類似於 MySQL 的 binlog)

 

由於 AOF 持久化的實時性更好,即當進程意外退出時丟失的數據更少,因此 AOF 是目前主流的持久化方式,不過 RDB 持久化仍然有其用武之地。

 

下面依次介紹 RDB 持久化和 AOF 持久化;由於 Redis 各個版本之間存在差異,如無特殊說明,以 Redis 3.0 為准。

 

 

RDB 持久化

 

RDB 持久化是將當前進程中的數據生成快照保存到硬盤(因此也稱作快照持久化),保存的文件后綴是 RDB;當 Redis 重新啟動時,可以讀取快照文件恢復數據。

 

 

觸發條件

 

RDB 持久化的觸發分為手動觸發和自動觸發兩種:

  • 手動觸發

  • 自動觸發

 

手動觸發:save 命令和 bgsave 命令都可以生成 RDB 文件。

 

save 命令會阻塞 Redis 服務器進程,直到 RDB 文件創建完畢為止,在 Redis 服務器阻塞期間,服務器不能處理任何命令請求。

而 bgsave 命令會創建一個子進程,由子進程來負責創建 RDB 文件,父進程(即 Redis 主進程)則繼續處理請求。

此時服務器執行日志如下:

bgsave 命令執行過程中,只有 fork 子進程時會阻塞服務器,而對於 save 命令,整個過程都會阻塞服務器。

 

因此 save 已基本被廢棄,線上環境要杜絕 save 的使用;后文中也將只介紹 bgsave 命令。

 

此外,在自動觸發 RDB 持久化時,Redis 也會選擇 bgsave 而不是 save 來進行持久化;下面介紹自動觸發 RDB 持久化的條件。

 

自動觸發:最常見的情況是在配置文件中通過 save m n,指定當 m 秒內發生 n 次變化時,會觸發 bgsave。

 

例如,查看 Redis 的默認配置文件(Linux 下為 Redis 根目錄下的 redis.conf),可以看到如下配置信息:

其中 save 900 1 的含義是:當時間到 900 秒時,如果 Redis 數據發生了至少 1 次變化,則執行 bgsave。

 

save 300 10 和 save 60 10000 同理,當三個 save 條件滿足任意一個時,都會引起 bgsave 的調用。

 

save m n 的實現原理:Redis 的 save m n,是通過 serverCron 函數、dirty 計數器和 lastsave 時間戳來實現的。

 

serverCron 是 Redis 服務器的周期性操作函數,默認每隔 100ms 執行一次;該函數對服務器的狀態進行維護,其中一項工作就是檢查 save m n 配置的條件是否滿足,如果滿足就執行 bgsave。

 

dirty 計數器是 Redis 服務器維持的一個狀態,記錄了上一次執行 bgsave/save 命令后,服務器狀態進行了多少次修改(包括增刪改);而當 save/bgsave 執行完成后,會將 dirty 重新置為 0。

 

例如,如果 Redis 執行了 set mykey helloworld,則 dirty 值會 +1;如果執行了 sadd myset v1 v2 v3,則 dirty 值會 +3;注意 dirty 記錄的是服務器進行了多少次修改,而不是客戶端執行了多少修改數據的命令。

 

lastsave 時間戳也是 Redis 服務器維持的一個狀態,記錄的是上一次成功執行 save/bgsave 的時間。

 

save m n 的原理如下:每隔 100ms,執行 serverCron 函數;在 serverCron 函數中,遍歷 save m n 配置的保存條件,只要有一個條件滿足,就進行 bgsave。

 

對於每一個 save m n 條件,只有下面兩條同時滿足時才算滿足:

  • 當前時間-lastsave > m

  • dirty >= n

 

save m n 執行日志:下圖是 save m n 觸發 bgsave 執行時,服務器打印日志的情況。

除了 save m n 以外,還有一些其他情況會觸發 bgsave:

  • 在主從復制場景下,如果從節點執行全量復制操作,則主節點會執行 bgsave 命令,並將 RDB 文件發送給從節點。

  • 執行 shutdown 命令時,自動執行 RDB 持久化,如下圖所示:

 

執行流程

 

前面介紹了觸發 bgsave 的條件,下面將說明 bgsave 命令的執行流程,如下圖所示:

圖片中的 5 個步驟所進行的操作如下:

  • Redis 父進程首先判斷:當前是否在執行 save 或 bgsave/bgrewriteaof(后面會詳細介紹該命令)的子進程,如果在執行則 bgsave 命令直接返回。

    bgsave/bgrewriteaof 的子進程不能同時執行,主要是基於性能方面的考慮:兩個並發的子進程同時執行大量的磁盤寫操作,可能引起嚴重的性能問題。

  • 父進程執行 fork 操作創建子進程,這個過程中父進程是阻塞的,Redis 不能執行來自客戶端的任何命令。

  • 父進程 fork 后,bgsave 命令返回”Background saving started”信息並不再阻塞父進程,並可以響應其他命令。

  • 子進程創建 RDB 文件,根據父進程內存快照生成臨時快照文件,完成后對原有文件進行原子替換。

  • 子進程發送信號給父進程表示完成,父進程更新統計信息。

 

 

RDB 文件

 

RDB 文件是經過壓縮的二進制文件,下面介紹關於 RDB 文件的一些細節。

 

存儲路徑

 

RDB 文件的存儲路徑既可以在啟動前配置,也可以通過命令動態設定。

 

配置:dir 配置指定目錄,dbfilename 指定文件名。默認是 Redis 根目錄下的 dump.rdb 文件。

 

動態設定:Redis 啟動后也可以動態修改 RDB 存儲路徑,在磁盤損害或空間不足時非常有用;執行命令為 config set dir {newdir}和 config set dbfilename {newFileName}。

 

如下所示(Windows 環境):

RDB 文件格式

 

RDB 文件格式如下圖所示:

其中各個字段的含義說明如下:

  • REDIS:常量,保存着“REDIS5 個字符。

  • db_version:RDB 文件的版本號,注意不是 Redis 的版本號。

  • SELECTDB 0 pairs:表示一個完整的數據庫(0 號數據庫),同理 SELECTDB 3 pairs 表示完整的 3 號數據庫。

    只有當數據庫中有鍵值對時,RDB 文件中才會有該數據庫的信息(上圖所示的 Redis 中只有 0 號和 3 號數據庫有鍵值對);如果 Redis 中所有的數據庫都沒有鍵值對,則這一部分直接省略。

    其中:SELECTDB 是一個常量,代表后面跟着的是數據庫號碼;0 和 3 是數據庫號碼;pairs 則存儲了具體的鍵值對信息,包括 key、value 值,及其數據類型、內部編碼、過期時間、壓縮信息等等。

  • EOF:常量,標志 RDB 文件正文內容結束。

  • check_sum:前面所有內容的校驗和;Redis 在載入 RBD 文件時,會計算前面的校驗和並與 check_sum 值比較,判斷文件是否損壞。

 

壓縮

 

Redis 默認采用 LZF 算法對 RDB 文件進行壓縮。雖然壓縮耗時,但是可以大大減小 RDB 文件的體積,因此壓縮默認開啟;可以通過命令關閉:

需要注意的是,RDB 文件的壓縮並不是針對整個文件進行的,而是對數據庫中的字符串進行的,且只有在字符串達到一定長度(20 字節)時才會進行。

 

 

啟動時加載

 

RDB 文件的載入工作是在服務器啟動時自動執行的,並沒有專門的命令。但是由於 AOF 的優先級更高,因此當 AOF 開啟時,Redis 會優先載入 AOF 文件來恢復數據。

 

只有當 AOF 關閉時,才會在 Redis 服務器啟動時檢測 RDB 文件,並自動載入。服務器載入 RDB 文件期間處於阻塞狀態,直到載入完成為止。

 

Redis 啟動日志中可以看到自動載入的執行:

Redis 載入 RDB 文件時,會對 RDB 文件進行校驗,如果文件損壞,則日志中會打印錯誤,Redis 啟動失敗。

 

 

RDB 常用配置總結

 

下面是 RDB 常用的配置項,以及默認值,前面介紹過的這里不再詳細介紹:

  • save m n:bgsave 自動觸發的條件;如果沒有 save m n 配置,相當於自動的 RDB 持久化關閉,不過此時仍可以通過其他方式觸發。

  • stop-writes-on-bgsave-error yes:當 bgsave 出現錯誤時,Redis 是否停止執行寫命令;設置為 yes,則當硬盤出現問題時,可以及時發現,避免數據的大量丟失。

    設置為 no,則 Redis 無視 bgsave 的錯誤繼續執行寫命令,當對 Redis 服務器的系統(尤其是硬盤)使用了監控時,該選項考慮設置為 no。

  • rdbcompression yes:是否開啟 RDB 文件壓縮。

  • rdbchecksum yes:是否開啟 RDB 文件的校驗,在寫入文件和讀取文件時都起作用;關閉 checksum 在寫入文件和啟動文件時大約能帶來 10% 的性能提升,但是數據損壞時無法發現。

  • dbfilename dump.rdb:RDB 文件名。

  • dir ./:RDB 文件和 AOF 文件所在目錄。

 

 

AOF 持久化

 

RDB 持久化是將進程數據寫入文件,而 AOF 持久化(即 Append Only File 持久化),則是將 Redis 執行的每次寫命令記錄到單獨的日志文件中(有點像 MySQL 的 binlog),當 Redis 重啟時再次執行 AOF 文件中的命令來恢復數據。

 

與 RDB 相比,AOF 的實時性更好,因此已成為主流的持久化方案。

 

 

開啟 AOF

 

Redis 服務器默認開啟 RDB,關閉 AOF;要開啟 AOF,需要在配置文件中配置:appendonly yes。

 

 

執行流程

 

由於需要記錄 Redis 的每條寫命令,因此 AOF 不需要觸發,下面介紹 AOF 的執行流程。

 

AOF 的執行流程包括:

  • 命令追加(append):將 Redis 的寫命令追加到緩沖區 aof_buf。

  • 文件寫入(write)和文件同步(sync):根據不同的同步策略將 aof_buf 中的內容同步到硬盤。

  • 文件重寫(rewrite):定期重寫 AOF 文件,達到壓縮的目的。

 

命令追加(append)

 

Redis 先將寫命令追加到緩沖區,而不是直接寫入文件,主要是為了避免每次有寫命令都直接寫入硬盤,導致硬盤 IO 成為 Redis 負載的瓶頸。

 

命令追加的格式是 Redis 命令請求的協議格式,它是一種純文本格式,具有兼容性好、可讀性強、容易處理、操作簡單避免二次開銷等優點,具體格式略。

 

在 AOF 文件中,除了用於指定數據庫的 select 命令(如 select 0 為選中 0 號數據庫)是由 Redis 添加的,其他都是客戶端發送來的寫命令。

 

文件寫入(write)和文件同步(sync)

 

Redis 提供了多種 AOF 緩存區的同步文件策略,策略涉及到操作系統的 write 函數和 fsync 函數,說明如下:

  • 為了提高文件寫入效率,在現代操作系統中,當用戶調用 write 函數將數據寫入文件時,操作系統通常會將數據暫存到一個內存緩沖區里,當緩沖區被填滿或超過了指定時限后,才真正將緩沖區的數據寫入到硬盤里。

    這樣的操作雖然提高了效率,但也帶來了安全問題:如果計算機停機,內存緩沖區中的數據會丟失。

    因此系統同時提供了 fsync、fdatasync 等同步函數,可以強制操作系統立刻將緩沖區中的數據寫入到硬盤里,從而確保數據的安全性。

 

AOF 緩存區的同步文件策略由參數 appendfsync 控制,各個值的含義如下:

  • always:命令寫入 aof_buf 后立即調用系統 fsync 操作同步到 AOF 文件,fsync 完成后線程返回。

    這種情況下,每次有寫命令都要同步到 AOF 文件,硬盤 IO 成為性能瓶頸,Redis 只能支持大約幾百 TPS 寫入,嚴重降低了 Redis 的性能。

    即便是使用固態硬盤(SSD),每秒大約也只能處理幾萬個命令,而且會大大降低 SSD 的壽命。

  • no:命令寫入 aof_buf 后調用系統 write 操作,不對 AOF 文件做 fsync 同步;同步由操作系統負責,通常同步周期為 30 秒。

    這種情況下,文件同步的時間不可控,且緩沖區中堆積的數據會很多,數據安全性無法保證。

  • everysec:命令寫入 aof_buf 后調用系統 write 操作,write 完成后線程返回;fsync 同步文件操作由專門的線程每秒調用一次。

    everysec 是前述兩種策略的折中,是性能和數據安全性的平衡,因此是 Redis 的默認配置,也是我們推薦的配置。

 

文件重寫(rewrite)

 

隨着時間流逝,Redis 服務器執行的寫命令越來越多,AOF 文件也會越來越大;過大的 AOF 文件不僅會影響服務器的正常運行,也會導致數據恢復需要的時間過長。

 

文件重寫是指定期重寫 AOF 文件,減小 AOF 文件的體積。需要注意的是,AOF 重寫是把 Redis 進程內的數據轉化為寫命令,同步到新的 AOF 文件;不會對舊的 AOF 文件進行任何讀取、寫入操作!

 

關於文件重寫需要注意的另一點是:對於 AOF 持久化來說,文件重寫雖然是強烈推薦的,但並不是必須的。即使沒有文件重寫,數據也可以被持久化並在 Redis 啟動的時候導入。

 

因此在一些實現中,會關閉自動的文件重寫,然后通過定時任務在每天的某一時刻定時執行。

 

文件重寫之所以能夠壓縮 AOF 文件,原因在於:

  • 過期的數據不再寫入文件。

  • 無效的命令不再寫入文件:如有些數據被重復設值(set mykey v1,set mykey v2)、有些數據被刪除了(sadd myset v1,del myset)等等。

  • 多條命令可以合並為一個:如 sadd myset v1,sadd myset v2sadd myset v3 可以合並為 sadd myset v1 v2 v3。

    不過為了防止單條命令過大造成客戶端緩沖區溢出,對於 list、set、hash、zset 類型的 key,並不一定只使用一條命令。

    而是以某個常量為界將命令拆分為多條。這個常量在 redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD 中定義,不可更改,3.0 版本中值是 64。

通過上述內容可以看出,由於重寫后 AOF 執行的命令減少了,文件重寫既可以減少文件占用的空間,也可以加快恢復速度。

 

文件重寫的觸發

 

文件重寫的觸發,分為手動觸發和自動觸發:

 

手動觸發,直接調用 bgrewriteaof 命令,該命令的執行與 bgsave 有些類似:都是 fork 子進程進行具體的工作,且都只有在 fork 時阻塞。

此時服務器執行日志如下:

自動觸發,根據 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 參數,以及 aof_current_size 和 aof_base_size 狀態確定觸發時機:

  • auto-aof-rewrite-min-size:執行 AOF 重寫時,文件的最小體積,默認值為 64MB。

  • auto-aof-rewrite-percentage:執行 AOF 重寫時,當前 AOF 大小(即 aof_current_size)和上一次重寫時 AOF 大小(aof_base_size)的比值。

 

其中,參數可以通過 config get 命令查看:

狀態可以通過 info persistence 查看:

只有當 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 兩個參數同時滿足時,才會自動觸發 AOF 重寫,即 bgrewriteaof 操作。

 

自動觸發 bgrewriteaof 時,可以看到服務器日志如下:

文件重寫的流程

 

文件重寫流程如下圖所示:

關於文件重寫的流程,有兩點需要特別注意:

  • 重寫由父進程 fork 子進程進行。

  • 重寫期間 Redis 執行的寫命令,需要追加到新的 AOF 文件中,為此 Redis 引入了 aof_rewrite_buf 緩存。

 

對照上圖,文件重寫的流程如下:

  • 1):Redis 父進程首先判斷當前是否存在正在執行 bgsave/bgrewriteaof 的子進程,如果存在則 bgrewriteaof 命令直接返回;如果存在 bgsave 命令則等 bgsave 執行完成后再執行,這個主要是基於性能方面的考慮。

  • 2):父進程執行 fork 操作創建子進程,這個過程中父進程是阻塞的。

  • 3.1):父進程 fork 后,bgrewriteaof 命令返回“Background append only file rewrite started”信息並不再阻塞父進程,並可以響應其他命令。

    Redis 的所有寫命令依然寫入 AOF 緩沖區,並根據 appendfsync 策略同步到硬盤,保證原有 AOF 機制的正確。

  • 3.2):由於 fork 操作使用寫時復制技術,子進程只能共享 fork 操作時的內存數據。

    由於父進程依然在響應命令,因此 Redis 使用 AOF 重寫緩沖區(圖中的 aof_rewrite_buf)保存這部分數據,防止新 AOF 文件生成期間丟失這部分數據。

    也就是說,bgrewriteaof 執行期間,Redis 的寫命令同時追加到 aof_buf 和 aof_rewirte_buf 兩個緩沖區。

  • 4):子進程根據內存快照,按照命令合並規則寫入到新的 AOF 文件。

  • 5.1):子進程寫完新的 AOF 文件后,向父進程發信號,父進程更新統計信息,具體可以通過 info persistence 查看。

  • 5.2):父進程把 AOF 重寫緩沖區的數據寫入到新的 AOF 文件,這樣就保證了新 AOF 文件所保存的數據庫狀態和服務器當前狀態一致。

  • 5.3):使用新的 AOF 文件替換老文件,完成 AOF 重寫。

 

 

啟動時加載

 

前面提到過,當 AOF 開啟時,Redis 啟動時會優先載入 AOF 文件來恢復數據;只有當 AOF 關閉時,才會載入 RDB 文件恢復數據。

 

當 AOF 開啟,且 AOF 文件存在時,Redis 啟動日志:

當 AOF 開啟,但 AOF 文件不存在時,即使 RDB 文件存在也不會加載(更早的一些版本可能會加載,但 3.0 不會),Redis 啟動日志如下:

文件校驗

 

與載入 RDB 文件類似,Redis 載入 AOF 文件時,會對 AOF 文件進行校驗,如果文件損壞,則日志中會打印錯誤,Redis 啟動失敗。

 

但如果是 AOF 文件結尾不完整(機器突然宕機等容易導致文件尾部不完整),且 aof-load-truncated 參數開啟,則日志中會輸出警告,Redis 忽略掉 AOF 文件的尾部,啟動成功。

 

aof-load-truncated 參數默認是開啟的:

偽客戶端

 

因為 Redis 的命令只能在客戶端上下文中執行,而載入 AOF 文件時命令是直接從文件中讀取的,並不是由客戶端發送。

 

因此 Redis 服務器在載入 AOF 文件之前,會創建一個沒有網絡連接的客戶端,之后用它來執行 AOF 文件中的命令,命令執行的效果與帶網絡連接的客戶端完全一樣。

 

 

AOF 常用配置總結

 

下面是 AOF 常用的配置項,以及默認值:

  • appendonly no:是否開啟 AOF。

  • appendfilename "appendonly.aof":AOF 文件名。

  • dir ./:RDB 文件和 AOF 文件所在目錄。

  • appendfsync everysec:fsync 持久化策略。

  • no-appendfsync-on-rewrite no:AOF 重寫期間是否禁止 fsync;如果開啟該選項,可以減輕文件重寫時 CPU 和硬盤的負載(尤其是硬盤),但是可能會丟失 AOF 重寫期間的數據;需要在負載和安全性之間進行平衡。

  • auto-aof-rewrite-percentage 100:文件重寫觸發條件之一。

  • auto-aof-rewrite-min-size 64mb:文件重寫觸發提交之一。

  • aof-load-truncated yes:如果 AOF 文件結尾損壞,Redis 啟動時是否仍載入 AOF 文件。

 

 

方案選擇與常見問題

 

前面介紹了 RDB 和 AOF 兩種持久化方案的細節,下面介紹 RDB 和 AOF 的特點、如何選擇持久化方案,以及在持久化過程中常遇到的問題等。

 

 

RDB 和 AOF 的優缺點

 

RDB 和 AOF 各有優缺點:

 

RDB 持久化

 

優點:RDB 文件緊湊,體積小,網絡傳輸快,適合全量復制;恢復速度比 AOF 快很多。當然,與 AOF 相比,RDB 最重要的優點之一是對性能的影響相對較小。

 

缺點:RDB 文件的致命缺點在於其數據快照的持久化方式決定了必然做不到實時持久化,而在數據越來越重要的今天,數據的大量丟失很多時候是無法接受的,因此 AOF 持久化成為主流。

 

此外,RDB 文件需要滿足特定格式,兼容性差(如老版本的 Redis 不兼容新版本的 RDB 文件)。

 

AOF 持久化

 

與 RDB 持久化相對應,AOF 的優點在於支持秒級持久化、兼容性好,缺點是文件大、恢復速度慢、對性能影響大。

 

 

持久化策略選擇

 

在介紹持久化策略之前,首先要明白無論是 RDB 還是 AOF,持久化的開啟都是要付出性能方面代價的:

  • 對於 RDB 持久化,一方面是 bgsave 在進行 fork 操作時 Redis 主進程會阻塞,另一方面,子進程向硬盤寫數據也會帶來 IO 壓力。

  • 對於 AOF 持久化,向硬盤寫數據的頻率大大提高(everysec 策略下為秒級),IO 壓力更大,甚至可能造成 AOF 追加阻塞問題(后面會詳細介紹這種阻塞)。

    此外,AOF 文件的重寫與 RDB 的 bgsave 類似,會有 fork 時的阻塞和子進程的 IO 壓力問題。

    相對來說,由於 AOF 向硬盤中寫數據的頻率更高,因此對 Redis 主進程性能的影響會更大。

 

在實際生產環境中,根據數據量、應用對數據的安全要求、預算限制等不同情況,會有各種各樣的持久化策略。

 

如完全不使用任何持久化、使用 RDB 或 AOF 的一種,或同時開啟 RDB 和 AOF 持久化等。

 

此外,持久化的選擇必須與 Redis 的主從策略一起考慮,因為主從復制與持久化同樣具有數據備份的功能,而且主機 master 和從機 slave 可以獨立的選擇持久化方案。

 

下面分場景來討論持久化策略的選擇,討論也只是作為參考,實際方案可能更復雜更具多樣性:

  • 如果 Redis 中的數據完全丟棄也沒有關系(如 Redis 完全用作 DB 層數據的 Cache),那么無論是單機,還是主從架構,都可以不進行任何持久化。

  • 在單機環境下(對於個人開發者,這種情況可能比較常見),如果可以接受十幾分鍾或更多的數據丟失,選擇 RDB 對 Redis 的性能更加有利;如果只能接受秒級別的數據丟失,應該選擇 AOF。

  • 但在多數情況下,我們都會配置主從環境,slave 的存在既可以實現數據的熱備,也可以進行讀寫分離分擔 Redis 讀請求,以及在 master 宕掉后繼續提供服務。

 

在這種情況下,一種可行的做法是:

  • master:完全關閉持久化(包括 RDB 和 AOF),這樣可以讓 master 的性能達到最好。

  • slave:關閉 RDB,開啟 AOF(如果對數據安全要求不高,開啟 RDB 關閉 AOF 也可以),並定時對持久化文件進行備份(如備份到其他文件夾,並標記好備份的時間)。

    然后關閉 AOF 的自動重寫,然后添加定時任務,在每天 Redis 閑時(如凌晨 12 點)調用 bgrewriteaof。

 

這里需要解釋一下,為什么開啟了主從復制,可以實現數據的熱備份,還需要設置持久化呢?

 

因為在一些特殊情況下,主從復制仍然不足以保證數據的安全,例如:

  • master 和 slave 進程同時停止:考慮這樣一種場景,如果 master 和 slave 在同一棟大樓或同一個機房,則一次停電事故就可能導致 master 和 slave 機器同時關機,Redis 進程停止;如果沒有持久化,則面臨的是數據的完全丟失。

  • master誤重啟:考慮這樣一種場景,master 服務因為故障宕掉了,如果系統中有自動拉起機制(即檢測到服務停止后重啟該服務)將 master 自動重啟,由於沒有持久化文件,那么 master 重啟后數據是空的,slave 同步數據也變成了空的;如果 master 和 slave 都沒有持久化,同樣會面臨數據的完全丟失。

    需要注意的是,即便是使用了哨兵(關於哨兵后面會有文章介紹)進行自動的主從切換,也有可能在哨兵輪詢到 master 之前,便被自動拉起機制重啟了。因此,應盡量避免“自動拉起機制”和“不做持久化”同時出現。

 

異地災備:上述討論的幾種持久化策略,針對的都是一般的系統故障,如進程異常退出、宕機、斷電等,這些故障不會損壞硬盤。

 

但是對於一些可能導致硬盤損壞的災難情況,如火災地震,就需要進行異地災備。

 

例如對於單機的情形,可以定時將 RDB 文件或重寫后的 AOF 文件,通過 scp 拷貝到遠程機器,如阿里雲、AWS 等。

 

對於主從的情形,可以定時在 master 上執行 bgsave,然后將 RDB 文件拷貝到遠程機器,或者在 slave 上執行 bgrewriteaof 重寫 AOF 文件后,將 AOF 文件拷貝到遠程機器上。

 

一般來說,由於 RDB 文件文件小、恢復快,因此災難恢復常用 RDB 文件;異地備份的頻率根據數據安全性的需要及其他條件來確定,但最好不要低於一天一次。

 

 

fork 阻塞:CPU 的阻塞

 

在 Redis 的實踐中,眾多因素限制了 Redis 單機的內存不能過大,例如:

  • 當面對請求的暴增,需要從庫擴容時,Redis 內存過大會導致擴容時間太長。

  • 當主機宕機時,切換主機后需要掛載從庫,Redis 內存過大導致掛載速度過慢。

  • 持久化過程中的 fork 操作。

 

首先說明一下 fork 操作:父進程通過 fork 操作可以創建子進程;子進程創建后,父子進程共享代碼段,不共享進程的數據空間,但是子進程會獲得父進程的數據空間的副本。

 

在操作系統 fork 的實際實現中,基本都采用了寫時復制技術,即在父/子進程試圖修改數據空間之前,父子進程實際上共享數據空間。

 

但是當父/子進程的任何一個試圖修改數據空間時,操作系統會為修改的那一部分(內存的一頁)制作一個副本。

 

雖然 fork 時,子進程不會復制父進程的數據空間,但是會復制內存頁表(頁表相當於內存的索引、目錄);父進程的數據空間越大,內存頁表越大,fork 時復制耗時也會越多。

 

在 Redis 中,無論是 RDB 持久化的 bgsave,還是 AOF 重寫的 bgrewriteaof,都需要 fork 出子進程來進行操作。

 

如果 Redis 內存過大,會導致 fork 操作時復制內存頁表耗時過多;而 Redis 主進程在進行 fork 時,是完全阻塞的,也就意味着無法響應客戶端的請求,會造成請求延遲過大。

 

對於不同的硬件、不同的操作系統,fork 操作的耗時會有所差別,一般來說,如果 Redis 單機內存達到了 10GB,fork 時耗時可能會達到百毫秒級別(如果使用 Xen 虛擬機,這個耗時可能達到秒級別)。

 

因此,一般來說 Redis 單機內存一般要限制在 10GB 以內;不過這個數據並不是絕對的,可以通過觀察線上環境 fork 的耗時來進行調整。

 

觀察的方法如下:執行命令 info stats,查看 latest_fork_usec 的值,單位為微秒。

 

為了減輕 fork 操作帶來的阻塞問題,除了控制 Redis 單機內存的大小以外,還可以適度放寬 AOF 重寫的觸發條件、選用物理機或高效支持 fork 操作的虛擬化技術等,例如使用 Vmware 或 KVM 虛擬機,不要使用 Xen 虛擬機。

 

 

AOF 追加阻塞:硬盤的阻塞

 

前面提到過,在 AOF 中,如果 AOF 緩沖區的文件同步策略為 everysec,則在主線程中,命令寫入 aof_buf 后調用系統 write 操作,write 完成后主線程返回。

 

fsync 同步文件操作由專門的文件同步線程每秒調用一次。這種做法的問題在於,如果硬盤負載過高,那么 fsync 操作可能會超過 1s。

 

如果 Redis 主線程持續高速向 aof_buf 寫入命令,硬盤的負載可能會越來越大,IO 資源消耗更快;如果此時 Redis 進程異常退出,丟失的數據也會越來越多,可能遠超過 1s。

 

為此,Redis 的處理策略是這樣的:主線程每次進行 AOF 會對比上次 fsync 成功的時間;如果距上次不到 2s,主線程直接返回;如果超過 2s,則主線程阻塞直到 fsync 同步完成。

 

因此,如果系統硬盤負載過大導致 fsync 速度太慢,會導致 Redis 主線程的阻塞;此外,使用 everysec 配置,AOF 最多可能丟失 2s 的數據,而不是 1s。

 

AOF 追加阻塞問題定位的方法:

  • 監控 info Persistence 中的 aof_delayed_fsync:當 AOF 追加阻塞發生時(即主線程等待 fsync 而阻塞),該指標累加。

  • AOF 阻塞時的 Redis 日志:Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.

  • 如果 AOF 追加阻塞頻繁發生,說明系統的硬盤負載太大,可以考慮更換 IO 速度更快的硬盤,或者通過 IO 監控分析工具對系統的 IO 負載進行分析,如 iostat(系統級 io)、iotop(io 版的 top)、pidstat 等。

 

 

info 命令與持久化

 

前面提到了一些通過 info 命令查看持久化相關狀態的方法,下面來總結一下。

 

info Persistence

 

執行結果如下:

其中比較重要的包括:

  • rdb_last_bgsave_status:上次 bgsave 執行結果,可以用於發現 bgsave 錯誤。

  • rdb_last_bgsave_time_sec:上次 bgsave 執行時間(單位是 s),可以用於發現 bgsave 是否耗時過長。

  • aof_enabled:AOF 是否開啟。

  • aof_last_rewrite_time_sec:上次文件重寫執行時間(單位是 s),可以用於發現文件重寫是否耗時過長。

  • aof_last_bgrewrite_status:上次 bgrewrite 執行結果,可以用於發現 bgrewrite 錯誤。

  • aof_buffer_length 和 aof_rewrite_buffer_length:AOF 緩存區大小和 AOF 重寫緩沖區大小。

  • aof_delayed_fsync:AOF 追加阻塞情況的統計。

 

info stats

 

其中與持久化關系較大的是:latest_fork_usec,代表上次 fork 耗時,可以參見前面的討論。

 

 

總結

 

本文主要內容可以總結如下:

  • 持久化在 Redis 高可用中的作用:數據備份,與主從復制相比強調的是由內存到硬盤的備份。

  • RDB 持久化:將數據快照備份到硬盤;介紹了其觸發條件(包括手動出發和自動觸發)、執行流程、RDB 文件等,特別需要注意的是文件保存操作由 fork 出的子進程來進行。

  • AOF 持久化:將執行的寫命令備份到硬盤(類似於 MySQL 的 binlog),介紹了其開啟方法、執行流程等,特別需要注意的是文件同步策略的選擇(everysec)、文件重寫的流程。

  • 一些現實的問題:包括如何選擇持久化策略,以及需要注意的 fork 阻塞、AOF 追加阻塞等。

 

 


●編號352,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

Web開發

更多推薦18個技術類公眾微信

涵蓋:程序人生、算法與數據結構、黑客技術與網絡安全、大數據技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、數據庫、運維等。

閱讀原文
閱讀   1006
5
 
精選留言

寫留言

  •  
    寫得不錯 很深入。贊👍
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM