深刻學習Redis(3):主從複製

前言

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

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

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

系列文章

深刻學習Redis(1):Redis內存模型github

深刻學習Redis(2):持久化redis

深刻學習Redis(3):主從複製
數據庫

深刻學習Redis(4):哨兵安全

深刻學習Redis(5):集羣服務器

目錄

1、主從複製概述網絡

2、如何使用主從複製併發

    1.    創建複製

    2.    實例

    3.    斷開復制

3、主從複製的實現原理

    1.    鏈接創建階段

    2.    數據同步階段

    3.    命令傳播階段

4、【數據同步階段】全量複製和部分複製

    1.    全量複製

    2.    部分複製

    3.    psync命令的執行

    4.    部分複製演示

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

    1.    主->從:PING

    2.    從->主:REPLCONF ACK

6、應用中的問題

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

    2.    複製超時問題

    3.    複製中斷問題

    4.    各場景下複製的選擇及優化技巧

    5.    複製相關的配置

    6.    單機內存大小限制

    7.    info Replication

7、總結

1、主從複製概述

主從複製,是指將一臺Redis服務器的數據,複製到其餘的Redis服務器。前者稱爲主節點(master),後者稱爲從節點(slave);數據的複製是單向的,只能由主節點到從節點。

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

主從複製的做用

主從複製的做用主要包括:

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

2、如何使用主從複製

爲了更直觀的理解主從複製,在介紹其內部原理以前,先說明咱們須要如何操做才能開啓主從複製。

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、主從複製的實現原理

上面一節中,介紹瞭如何操做能夠創建主從關係;本小節將介紹主從複製的實現原理。

主從複製過程大致能夠分爲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。

4、【數據同步階段】全量複製和部分複製

在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. 部分複製演示

在下面的演示中,網絡中斷幾分鐘後恢復,斷開鏈接的主從節點進行了部分複製;爲了便於模擬網絡中斷,本例中的主從節點在局域網中的兩臺機器上。

網絡中斷

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

主節點日誌以下:

從節點日誌以下:

網絡恢復

網絡恢復後,從節點鏈接主節點成功,並請求進行部分複製,主節點接收請求後,兩者進行部分複製以同步數據。

主節點日誌以下:

從節點日誌以下:

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

在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制: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值。

6、應用中的問題

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集羣同時提升讀負載能力和寫負載能力等。若是使用讀寫分離,可使用哨兵,使主從節點的故障切換儘量自動化,並減小對應用程序的侵入。

2. 複製超時問題

主從節點複製超時是致使複製中斷的最重要的緣由之一,本小節單獨說明超時問題,下一小節說明其餘會致使複製中斷的問題。

超時判斷意義

在複製鏈接創建過程當中及以後,主從節點都有機制判斷鏈接是否超時,其意義在於:

(1)若是主節點判斷鏈接超時,其會釋放相應從節點的鏈接,從而釋放各類資源,不然無效的從節點仍會佔用主節點的各類資源(輸出緩衝區、帶寬、鏈接等);此外鏈接超時的判斷可讓主節點更準確的知道當前有效從節點的個數,有助於保證數據安全(配合前面講到的min-slaves-to-write等參數)。

(2)若是從節點判斷鏈接超時,則能夠及時從新創建鏈接,避免與主節點數據長期的不一致。

判斷機制

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

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

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

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

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

/* 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中展現的大部份內容在文章中都已經講述,這裏再也不詳述。

7、總結

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

一、主從複製的做用:宏觀的瞭解主從複製是爲了解決什麼樣的問題,即數據冗餘、故障恢復、讀負載均衡等。

二、主從複製的操做:即slaveof命令。

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

四、應用中的問題:包括讀寫分離的問題(數據不一致問題、數據過時問題、故障切換問題等)、複製超時問題、複製中斷問題等,而後總結了主從複製相關的配置,其中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

 

創做不易,若是文章對你有幫助,就點個贊、評個論唄~

創做不易,若是文章對你有幫助,就點個贊、評個論唄

創做不易,若是文章對你有幫助,就點個贊、評個論唄~

相關文章
相關標籤/搜索