Redis常見知識彙總(二)

目錄

 

五、主從複製

六、哨兵機制

七、集羣

 


五、主從複製

單機有什麼問題?

單機即在一臺機器上部署一個redis節點,主要會存在以下問題:

1. 機器故障

如果發生機器故障,例如磁盤損壞,主板損壞等,未能在短時間內修復好,客戶端將無法連接redis。
當然如果僅僅是redis節點掛掉了,可以進行問題排查然後重啓,姑且不考慮這段時間對外服務的可用性,那還是可以接受的。
而發生機器故障,基本是無濟於事。除非把redis遷移到另一臺機器上,並且還要考慮數據同步的問題。

2. 容量瓶頸

假如一臺機器是16G內存,redis使用了12G內存,而其他應用還需要使用內存,假設我們總共需要60G內存要如何去做呢,是否有必要購買64G內存的機器?

3. QPS瓶頸

redis官方數據顯示可以達到10w的QPS,如果業務需要100w的QPS怎麼去做呢?

關於容量瓶頸和QPS瓶頸是redis分佈式需要解決的問題,而機器故障就是高可用的問題了。

主從複製的作用

一主一從

如圖所示左邊是Master節點,右邊是slave節點,即主節點和從節點。從節點也是可以對外提供服務的,主節點是有數據的,從節點可以通過複製操作將主節點的數據同步過來,並且隨着主節點數據不斷寫入,從節點數據也會做同步的更新。
整體起到的就是數據備份的效果。

一主多從

除了一主一從模型之外,redis還提供了一主多從的模型,也就是一個master可以有多個slave,也就相當於有了多份的數據副本。
這樣可以做一個更加高可用的選擇,例如一個master和一個slave掛掉了,還能有其他的slave數據備份。

讀寫分離

除了作爲數據備份,主從模型還能做另外一個功能,就是讀寫分離。
讓master節點負責提供寫服務,而將數據讀取的壓力進行分流和負載,分攤給所有的從節點。

主從複製的作用

  1. 數據副本(備份)
  2. 擴展讀性能(讀寫分離)

簡單總結

  1. 一個master可以有多個slave
  2. 一個slave只能有一個master
  3. 數據流向是單向的,master到slave

主從複製的配置

1. slaveof命令

如圖,想讓6380節點成爲6379的從節點,只需要執行 slaveof 命令即可,此複製命令是異步進行的,redis會自動進行後續數據複製的操作。
注:一般生產環境不允許主從節點都在一臺機器上,因爲沒有任何的價值。

取消複製

如果6380節點不希望成爲6379的從節點,可以執行 slave of on one 命令,取消後6380節點的數據不會被清除,只是說後續6379節點新寫入的數據不會再同步到該節點了。
注意:如果取消複製後想slave一個新的主節點,新的主節點在同步給slave節點數據時,會先將從節點的數據全部清除

 

2. 修改配置

# 配置主節點的IP和端口號
slaveof ip port
 # 從節點只做讀的操作,保證主從數據的一致性
slave-read-only yes 

對比

runid和複製偏移量

redis每次啓動的時候都會有一個隨機的ID,作爲一個標識,這個ID就是runid,當然重啓之後值就改變了。
查看runid:redis-cli -p 6379 info | grep run
假如端口爲6380的redis去複製6379,知道runid後,在6380上做一個標識,如果runid改變了,說明主可能重啓了或者發生了其它變化,這時候就可以做一個全量複製把數據同步過來。或者第一次啓動時根本不知道6379的runid,也會進行全量複製

偏移量:數據寫入量的字節
比如主執行set hello world,就會有一個偏移量,然後從同步數據,也會記錄一個偏移量
當兩個偏移量達到一致時候,實際上數據就是完全同步的狀態。

啓動主從redis,並在寫入命令執行前後查看主偏移量:
redis-cli -p 6379 info replication | grep master_repl

從節點會向主節點做一個上報,把從節點的狀態同步給主節點,這樣主節點就知道了從節點的偏移量

生產環境我們一般不關心這個值,有時候做監控的時候會比對一下這兩個值的差,如果差的太多,說明主從是有問題的。

全量複製

全量複製主節點會將RDB文件也就是當前狀態去同步給slave,在此期間主新寫入的命令會單獨記錄起來,然後當RDB文件加載完畢之後,會通過偏移量對比將這個期間產生的寫入值同步給slave,這樣就能達到數據完全同步的效果

全量複製過程

  1. 在其內部有一條命令psync,是做同步的命令,它可以完成全量複製和部分複製的功能,當啓動slave節點時,它會發送psync命令給主節點,需要傳遞兩個參數,runidoffset(偏移量),也就是從向主傳遞主節點的runid以及自己的偏移量,對於第一次複製而言,就直接傳遞?和 -1,當然這個參數是由slave內部傳的。
  2. master接收到命令後知道從希望做全量複製,主就會將自己的runid和offset傳遞給從
  3. slave節點保存master的基本信息
  4. master執行bgsave生成RDB文件,並且在此期間新產生的寫入命令會被記錄到repl_back_buffer(複製緩衝區)
  5. 主向從傳輸RDB文件
  6. 主向從發送複製緩衝區內容
  7. 清空從節點舊的數據
  8. 從節點加載RDB文件到內存中,同時加載緩衝區數據

全量複製的開銷

實際上全量複製的開銷是非常大的,主要體現在如下方面

  1. bgsave時間(對cpu、 內存、硬盤都會有一定的開銷)
  2. RDB文件網絡傳輸時間(網絡帶寬)
  3. 從節點清空數據時間(根據從節點的數據規模)
  4. 從節點加載RDB的時間
  5. 可能的AOF重寫時間(在最後從加載完RDB之後如果開啓了AOF,會做AOF重寫)

全量複製除了上述開銷之外,還會有個問題:
假如master和slave網絡發生了抖動,那一段時間內這些數據就會丟失,對於slave來說這段時間master更新的數據是不知道的。最簡單的方式就是再做一次全量複製,從而獲取到最新的數據,在redis2.8之前是這麼做的。

部分複製

部分複製,redis2.8之後提供。如果發生類似抖動時候,可以有一種機制將這種損失降低到最低,如何實現的?

  1. 如果發生了抖動,相當於連接斷開了
  2. 主會將寫命令記錄到緩衝區,repl_back_buffer
  3. 當slave再次去連接master時候,就是說網絡抖動結束之後,會觸發增量複製
  4. 從會執行pysnc命令,將當前自己的offset和主的runid傳遞給master
  5. 如果發現傳輸的offset偏移量是在buffer內的,不在期間內就證明你已經錯過了很多數據,buffer也是有限的,默認是1M,會將offset開始到隊列結束的數據同步給從。這樣master和slave就達到了一致。

通過部分複製(增量複製)有效的降低了全量複製的開銷。

複製的相關配置

無磁盤化複製

# 開啓無磁盤化複製
repl-diskless-sync yes
# 當收到第一個複製請求時,等待 5s 後再開始複製,因爲要等更多 slave 一起連接過來
repl-diskless-sync-delay 5

複製的限制

如果在複製期間,rdb複製時間超過60秒,內存緩衝區持續消耗超過64MB,或者一次性超過256MB,那麼將會停止複製(失敗)
配置項:client-output-buffer-limit slave 256MB 64MB 60

 

 主從複製概述

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

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

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

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

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

回到頂部

如何使用主從複製

開啓主從複製

主從複製的開啓,完全是在從節點發起的;不需要我們在主節點做任何事情。

從節點開啓主從複製,有3種方式:

  • 配置文件:在從服務器的配置文件中加入:slaveof <masterip> <masterport>
  • 啓動命令:redis-server啓動命令後加入 --slaveof <masterip> <masterport>
  • 客戶端命令:Redis服務器啓動後,直接通過客戶端執行命令:slaveof <masterip> <masterport>,則該Redis實例成爲從節點。

斷開主從複製

通過slaveof <masterip> <masterport>命令建立主從複製關係以後,可以通過slaveof no one斷開。

從節點斷開復制後,不會刪除已有的數據,只是不再接受主節點新的數據變化。

回到頂部

主從複製的實現原理

主從複製過程大體可以分爲3個階段:連接建立階段(即準備階段)數據同步階段命令傳播階段

連接建立階段

step1:保存主節點信息

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

slaveof是異步命令,從節點完成主節點ip和port的保存後,向發送slaveof命令的客戶端直接返回OK,實際的複製操作在這之後纔開始進行。

step2:建立socket連接

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

如果連接成功:

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

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

step3:發送ping命令

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

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

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

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

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

step4:身份驗證

如果從節點中設置了masterauth選項,則從節點需要向主節點進行身份驗證;沒有設置該選項,則不需要驗證。

從節點進行身份驗證是通過向主節點發送auth命令進行的,auth命令的參數即爲配置文件中的masterauth的值。

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

step5:發送從節點端口信息

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

數據同步階段

主從節點之間的連接建立以後,便可以開始進行數據同步,該階段可以理解爲從節點數據的初始化。

具體執行的方式是:從節點向主節點發送psync命令,開始同步。

數據同步階段是主從複製最核心的階段,根據主從節點當前狀態的不同,可以分爲全量複製和部分複製

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

 命令傳播階段

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

在命令傳播階段,除了發送寫命令,主從節點還維持着心跳機制:PING和REPLCONF ACK。

PS:

延遲與不一致:命令傳播是異步的過程,即主節點發送寫命令後並不會等待從節點的回覆;因此實際上主從節點之間很難保持實時的一致性,延遲在所難免。數據不一致的程度,與主從節點之間的網絡狀況、主節點寫命令的執行頻率、以及主節點中的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命令請求同步數據,此時根據主從節點當前狀態的不同,同步方式可能是全量複製或部分複製。

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

全量複製

Redis通過psync命令進行全量複製的過程如下:

(1)從節點判斷無法進行部分複製,向主節點發送全量複製的請求;或從節點發送部分複製的請求,但主節點判斷無法進行全量複製;

(2)主節點收到全量複製的命令後,執行bgsave,在後臺生成RDB文件,並使用一個緩衝區(稱爲複製緩衝區)記錄從現在開始執行的所有寫命令

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

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

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

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

(1)主節點通過bgsave命令fork子進程進行RDB持久化,該過程是非常消耗CPU、內存(頁表複製)、硬盤IO的;

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

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

部分複製

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

部分複製的實現,依賴於三個重要的概念:複製偏移量,複製積壓緩衝區,服務器運行ID

複製偏移量

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

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

複製積壓緩衝區

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

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

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

從節點將offset發送給主節點後,主節點根據offset和緩衝區大小決定能否執行部分複製:

  • 如果offset偏移量之後的數據,仍然都在複製積壓緩衝區裏,則執行部分複製;
  • 如果offset偏移量之後的數據已不在複製積壓緩衝區中(數據已被擠出),則執行全量複製。

服務器運行ID(runid)

每個Redis節點(無論主從),在啓動時都會自動生成一個隨機ID(每次啓動都不一樣),由40個隨機的十六進制字符組成;

runid用來唯一識別一個Redis節點。通過info Server命令,可以查看節點的runid。

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

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

psync命令的執行

(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,從節點保存這兩個值,以備使用。

回到頂部

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

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

主->從:PING

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

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

從->主: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值。

回到頂部

應用中的問題

讀寫分離及其中的問題

在主從複製基礎上實現的讀寫分離,可以實現Redis的讀負載均衡:由主節點提供寫服務,由一個或多個從節點提供讀服務(多個從節點既可以提高數據冗餘程度,也可以最大化讀負載能力);

在使用Redis讀寫分離時需要注意的問題。

延遲與不一致問題

由於主從複製的命令傳播是異步的,延遲與數據的不一致不可避免。如果應用對數據不一致的接受程度程度較低,可能的優化措施包括:

優化主從節點之間的網絡環境(如在同機房部署);

監控主從節點延遲(通過offset)判斷,如果從節點延遲過大,通知應用不再通過該從節點讀取數據;

使用集羣同時擴展寫負載和讀負載等。

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

數據過期問題

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

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

在主從複製場景下,爲了主從節點的數據一致性,從節點不會主動刪除數據,而是由主節點控制從節點中過期數據的刪除。由於主節點的惰性刪除和定期刪除策略,都不能保證主節點及時對過期數據執行刪除操作,因此,當客戶端通過Redis從節點讀取數據時,很容易讀取到已經過期的數據。Redis 3.2中,從節點在讀取數據時,增加了對數據是否過期的判斷:如果該數據已過期,則不返回給客戶端;將Redis升級到3.2可以解決數據過期問題。

故障切換問題

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

 複製超時問題

主從節點複製超時是導致複製中斷的最重要的原因之一。

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

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

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

判斷機制

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

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

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

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

複製階段連接超時有關的一些實際問題:

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

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

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

複製緩衝區溢出

在全量複製階段,主節點會將執行的寫命令放到複製緩衝區中,該緩衝區存放的數據包括了以下幾個時間段內主節點執行的寫命令: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也可以生效)。

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

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

第一次建立複製

此時全量複製不可避免,但仍有幾點需要注意:

如果主節點的數據量較大,應該儘量避開流量的高峯期,避免造成阻塞;

如果有多個從節點需要建立對主節點的複製,可以考慮將幾個從節點錯開,避免主節點帶寬佔用過大。

如果從節點過多,也可以調整主從複製的拓撲結構,由一主多從結構變爲樹狀結構(中間的節點既是其主節點的從節點,也是其從節點的主節點);

但使用樹狀結構應該謹慎:雖然主節點的直接從節點減少,降低了主節點的負擔,但是多層從節點的延遲增大,數據一致性變差;且結構複雜,維護相當困難。

主節點重啓

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

主節點宕機

主節點宕機重啓後,runid會發生變化,因此不能進行部分複製,只能全量複製。

實際上在主節點宕機的情況下,應進行故障轉移處理,將其中的一個從節點升級爲主節點,其他從節點從新的主節點進行復制;且故障轉移應儘量的自動化。

安全重啓:debug reload

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

爲了解決這個問題,Redis提供了debug reload的重啓方式:重啓後,主節點的runid和offset都不受影響,避免了全量複製。

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

從節點重啓

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

網絡中斷

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

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

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

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

複製相關的配置

slaveof <masterip> <masterport>:Redis啓動時起作用;作用是建立複製關係,開啓了該配置的Redis服務器在啓動後成爲從節點。該註釋默認註釋掉,即Redis服務器默認都是主節點。

repl-timeout 60:與各個階段主從節點連接超時判斷有關。

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

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

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

repl-disable-tcp-nodelay no:與命令傳播階段的延遲有關。

masterauth <master-password>:與連接建立階段的身份驗證有關。

repl-ping-slave-period 10:與命令傳播階段主從節點的超時判斷有關。

repl-backlog-size 1mb:複製積壓緩衝區的大小。

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

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

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

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

單機內存大小限制

fork操作對Redis單機內存大小的限制。實際上在Redis的使用中,限制單機內存大小的因素非常之多,單機內存過大可能造成的影響:

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

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

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

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

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

 

六、哨兵機制

一、作用和架構

 

1.作用

 

在介紹哨兵之前,首先從宏觀角度回顧一下Redis實現高可用相關的技術。它們包括:持久化、複製、哨兵和集羣,其主要作用和解決的問題是:

 

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

  • 複製:複製是高可用Redis的基礎,哨兵和集羣都是在複製基礎上實現高可用的。複製主要實現了數據的多機備份,以及對於讀操作的負載均衡和簡單的故障恢復。缺陷是故障恢復無法自動化;寫操作無法負載均衡;存儲能力受到單機的限制。

  • 哨兵:在複製的基礎上,哨兵實現了自動化的故障恢復。缺陷是寫操作無法負載均衡;存儲能力受到單機的限制。

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

 

詳細內容可回顧:

 

 

下面說回哨兵。

 

Redis Sentinel,即Redis哨兵,在Redis 2.8版本開始引入。哨兵的核心功能是主節點的自動故障轉移。下面是Redis官方文檔對於哨兵功能的描述:

 

  • 監控(Monitoring):哨兵會不斷地檢查主節點和從節點是否運作正常。

  • 自動故障轉移(Automatic Failover):當主節點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節點的其中一個從節點升級爲新的主節點,並讓其他從節點改爲複製新的主節點。

  • 配置提供者(Configuration Provider):客戶端在初始化時,通過連接哨兵來獲得當前Redis服務的主節點地址。

  • 通知(Notification):哨兵可以將故障轉移的結果發送給客戶端。

 

其中,監控和自動故障轉移功能,使得哨兵可以及時發現主節點故障並完成轉移;而配置提供者和通知功能,則需要在與客戶端的交互中才能體現。

 

這裏對“客戶端”一詞在文章中的用法做一個說明:在前面的文章中,只要通過API訪問Redis服務器,都會稱作客戶端,包括Redis-cli、Java客戶端Jedis等。爲了便於區分說明,本文中的客戶端並不包括Redis-cli,而是比Redis-cli更加複雜:Redis-cli使用的是Redis提供的底層接口,而客戶端則對這些接口、功能進行了封裝,以便充分利用哨兵的配置提供者和通知功能。

 

2.架構

 

典型的哨兵架構圖如下所示:

 

 

它由兩部分組成:

 

  • 哨兵節點:哨兵系統由一個或多個哨兵節點組成,哨兵節點是特殊的Redis節點,不存儲數據。

  • 數據節點:主節點和從節點都是數據節點。

 

二、部署

 

這一部分將部署一個簡單的哨兵系統,包含1個主節點、2個從節點和3個哨兵節點。方便起見,所有這些節點都部署在一臺機器上(局域網IP:192.168.92.128),使用端口號區分;且節點的配置儘可能簡化。

 

1.部署主從節點

 

哨兵系統中的主從節點,與普通的主從節點配置是一樣的,並不需要做任何額外配置。下面分別是主節點(port=6379)和2個從節點(port=6380/6381)的配置文件,配置都比較簡單,不再詳述:

 

#redis-6379.conf

port 6379

daemonize yes

logfile "6379.log"

dbfilename "dump-6379.rdb"

 

#redis-6380.conf

port 6380

daemonize yes

logfile "6380.log"

dbfilename "dump-6380.rdb"

slaveof 192.168.92.128 6379

 

#redis-6381.conf

port 6381

daemonize yes

logfile "6381.log"

dbfilename "dump-6381.rdb"

slaveof 192.168.92.128 6379

 

配置完成後,依次啓動主節點和從節點:

 

redis-server redis-6379.conf

redis-server redis-6380.conf

redis-server redis-6381.conf

 

節點啓動後,連接主節點查看主從狀態是否正常,如下圖所示:

 

 

2.部署哨兵節點

 

哨兵節點本質上是特殊的Redis節點。

 

3個哨兵節點的配置幾乎是完全一樣的,主要區別在於端口號的不同(26379 / 26380 / 263 81),下面以26379節點爲例介紹節點的配置和啓動方式;配置部分儘量簡化,更多配置會在後面介紹:

 

#sentinel-26379.conf

port 26379

daemonize yes

logfile "26379.log"

sentinel monitor mymaster 192.168.92.128   6379 2

 

其中,sentinel monitor mymaster 192.168. 92.128 6379 2配置的含義是:該哨兵節點監控192.168.92.128:6379這個主節點,該主節點的名稱是mymaster,最後的2的含義與主節點的故障判定有關:至少需要2個哨兵節點同意,才能判定主節點故障並進行故障轉移。

 

哨兵節點的啓動有兩種方式,二者作用是完全相同的:

 

redis-sentinel sentinel-26379.conf

redis-server   sentinel-26379.conf --sentinel

 

按照上述方式配置和啓動之後,整個哨兵系統就啓動完畢了。可以通過Redis-cli連接哨兵節點進行驗證,如下圖所示:可以看出26379哨兵節點已經在監控mymaster主節點(即192.168.92.128:6379),並發現了其2個從節點和另外2個哨兵節點。

 

 

此時如果查看哨兵節點的配置文件,會發現一些變化,以26379爲例:

 

 

其中,dir只是顯式聲明瞭數據和日誌所在的目錄(在哨兵語境下只有日誌);known-slave和known-sentinel顯示哨兵已經發現了從節點和其他哨兵;帶有epoch的參數與配置紀元有關(配置紀元是一個從0開始的計數器,每進行一次領導者哨兵選舉,都會+1;領導者哨兵選舉是故障轉移階段的一個操作,在後文原理部分會介紹)。

 

3.演示故障轉移

 

哨兵的4個作用中,配置提供者和通知需要客戶端的配合,本文將在下一章介紹客戶端訪問哨兵系統的方法時詳細介紹。這一小節將演示當主節點發生故障時,哨兵的監控和自動故障轉移功能。

 

Step1:首先,使用kill命令殺掉主節點:

 

 

Step2:如果此時立即在哨兵節點中使用info Sentinel命令查看,會發現主節點還沒有切換過來,因爲哨兵發現主節點故障並轉移,需要一段時間。

 

 

Step3:一段時間以後,再次在哨兵節點中執行info Sentinel查看,發現主節點已經切換成6380節點。

 

 

但是同時可以發現,哨兵節點認爲新的主節點仍然有2個從節點,這是因爲哨兵在將6380切換成主節點的同時,將6379節點置爲其從節點;雖然6379從節點已經掛掉,但是由於哨兵並不會對從節點進行客觀下線(其含義將在原理部分介紹),因此認爲該從節點一直存在。當6379節點重新啓動後,會自動變成6380節點的從節點。下面驗證一下。

 

Step4:重啓6379節點,可以看到6379節點成爲了6380節點的從節點。

 

 

Step5:在故障轉移階段,哨兵和主從節點的配置文件都會被改寫。

 

對於主從節點,主要是slaveof配置的變化:新的主節點沒有了slaveof配置,其從節點則slaveof新的主節點。

 

對於哨兵節點,除了主從節點信息的變化,紀元(epoch)也會變化,下圖中可以看到紀元相關的參數都+1了。

 

 

4.總結

 

哨兵系統的搭建過程,有幾點需要注意:

 

  • 哨兵系統中的主從節點,與普通的主從節點並沒有什麼區別,故障發現和轉移是由哨兵來控制和完成的。

  • 哨兵節點本質上是Redis節點。

  • 每個哨兵節點,只需要配置監控主節點,便可以自動發現其他的哨兵節點和從節點。

  • 在哨兵節點啓動和故障轉移階段,各個節點的配置文件會被重寫(Config Rewrite)。

  • 本章的例子中,一個哨兵只監控了一個主節點;實際上,一個哨兵可以監控多個主節點,通過配置多條sentinel monitor即可實現。

 

三、客戶端訪問哨兵系統

 

上一小節演示了哨兵的兩大作用:監控和自動故障轉移,本小節則結合客戶端演示哨兵的另外兩個作用:配置提供者和通知。

 

1.代碼示例

 

在介紹客戶端的原理之前,先以Java客戶端Jedis爲例,演示一下使用方法:下面代碼可以連接我們剛剛搭建的哨兵系統,並進行各種讀寫操作:

 

public static void testSentinel() throws Exception   {

         String   masterName = "mymaster";

         Set<String>   sentinels = new HashSet<>();

         sentinels.add("192.168.92.128:26379");

         sentinels.add("192.168.92.128:26380");

         sentinels.add("192.168.92.128:26381");

 

         JedisSentinelPool   pool = new JedisSentinelPool(masterName, sentinels); //初始化過程做了很多工作

         Jedis   jedis = pool.getResource();

         jedis.set("key1", "value1");

         pool.close();

}

(注:代碼中只演示如何連接哨兵,異常處理、資源關閉等未考慮)

 

2.客戶端原理

 

Jedis客戶端對哨兵提供了很好的支持。如上述代碼所示,我們只需要向Jedis提供哨兵節點集合和masterName,構造Jedis SentinelPool對象;然後便可以像使用普通Redis連接池一樣來使用了:通過pool.getResource()獲取連接,執行具體的命令。

 

在整個過程中,我們的代碼不需要顯式的指定主節點的地址,就可以連接到主節點;代碼中對故障轉移沒有任何體現,就可以在哨兵完成故障轉移後自動的切換主節點。之所以可以做到這一點,是因爲在JedisSentinelPool的構造器中,進行了相關的工作,主要包括以下兩點:

 

遍歷哨兵節點,獲取主節點信息:遍歷哨兵節點,通過其中一個哨兵節點+masterName獲得主節點的信息;該功能是通過調用哨兵節點的sentinel get-master-addr-by-name命令實現,該命令示例如下:

 

 

一旦獲得主節點信息,停止遍歷(因此一般來說遍歷到第一個哨兵節點,循環就停止了)。

 

增加對哨兵的監聽:這樣當發生故障轉移時,客戶端便可以收到哨兵的通知,從而完成主節點的切換。具體做法是:利用Redis提供的發佈訂閱功能,爲每一個哨兵節點開啓一個單獨的線程,訂閱哨兵節點的+switch-master頻道,當收到消息時,重新初始化連接池。

 

3.總結

 

通過客戶端原理的介紹,可以加深對哨兵功能的理解,如下:

 

配置提供者:客戶端可以通過哨兵節點+masterName獲取主節點信息,在這裏哨兵起到的作用就是配置提供者。

 

需要注意的是,哨兵只是配置提供者,而不是代理。二者的區別在於:

 

  • 如果是配置提供者,客戶端在通過哨兵獲得主節點信息後,會直接建立到主節點的連接,後續的請求(如set/get)會直接發向主節點;

  • 如果是代理,客戶端的每一次請求都會發向哨兵,哨兵再通過主節點處理請求。

 

舉一個例子可以很好的理解哨兵的作用是配置提供者,而不是代理。在前面部署的哨兵系統中,將哨兵節點的配置文件進行如下修改:

 

sentinel monitor   mymaster 192.168.92.128 6379 2

改爲

sentinel monitor mymaster 127.0.0.1 6379 2

 

然後,將前述客戶端代碼在局域網的另外一臺機器上運行,會發現客戶端無法連接主節點;這是因爲哨兵作爲配置提供者,客戶端通過它查詢到主節點的地址爲127.0.0.1:6379,客戶端會向127.0.0.1:6379建立Redis連接,自然無法連接。如果哨兵是代理,這個問題就不會出現了。

 

通知:哨兵節點在故障轉移完成後,會將新的主節點信息發送給客戶端,以便客戶端及時切換主節點。

 

四、基本原理

 

前面介紹了哨兵部署、使用的基本方法,本部分介紹哨兵實現的基本原理。

 

1.哨兵節點支持的命令

 

哨兵節點作爲運行在特殊模式下的Redis節點,其支持的命令與普通的Redis節點不同。在運維中,我們可以通過這些命令查詢或修改哨兵系統;不過更重要的是,哨兵系統要實現故障發現、故障轉移等各種功能,離不開哨兵節點之間的通信,而通信的很大一部分是通過哨兵節點支持的命令來實現的。下面介紹哨兵節點支持的主要命令:

 

基礎查詢:

 

通過這些命令,可以查詢哨兵系統的拓撲結構、節點信息、配置信息等。

 

  • info sentinel:獲取監控的所有主節點的基本信息。

  • sentinel masters:獲取監控的所有主節點的詳細信息。

  • sentinel master mymaster:獲取監控的主節點mymaster的詳細信息。

  • sentinel slaves mymaster:獲取監控的主節點mymaster的從節點的詳細信息。

  • sentinel sentinels mymaster:獲取監控的主節點mymaster的哨兵節點的詳細信息。

  • sentinel get - master - addr - by- name mymaster:獲取監控的主節點mymaster的地址信息,前文已有介紹。

  • sentinel is-master-down-by-addr:哨兵節點之間可以通過該命令詢問主節點是否下線,從而對是否客觀下線做出判斷。

 

增加/移除對主節點的監控:

 

sentinel monitor mymaster2 192.168.92.128 16379 2:與部署哨兵節點時配置文件中的sentinel monitor功能完全一樣,不再詳述。

 

sentinel remove mymaster2:取消當前哨兵節點對主節點mymaster2的監控。

 

強制故障轉移:

 

sentinel failover mymaster:該命令可以強制對mymaster執行故障轉移,即便當前的主節點運行完好;例如,如果當前主節點所在機器即將報廢,便可以提前通過failover命令進行故障轉移。

 

2.基本原理

 

關於哨兵的原理,關鍵是瞭解以下幾個概念:

 

定時任務:每個哨兵節點維護了3個定時任務。定時任務的功能分別如下:通過向主從節點發送info命令獲取最新的主從結構;通過發佈訂閱功能獲取其他哨兵節點的信息;通過向其他節點發送ping命令進行心跳檢測,判斷是否下線。

 

主觀下線:在心跳檢測的定時任務中,如果其他節點超過一定時間沒有回覆,哨兵節點就會將其進行主觀下線。顧名思義,主觀下線的意思是一個哨兵節點“主觀地”判斷下線;與主觀下線相對應的是客觀下線。

 

客觀下線:哨兵節點在對主節點進行主觀下線後,會通過sentinel is-master-down-by-addr命令詢問其他哨兵節點該主節點的狀態;如果判斷主節點下線的哨兵數量達到一定數值,則對該主節點進行客觀下線。

 

需要特別注意的是,客觀下線是主節點纔有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操作。

 

選舉領導者哨兵節點:當主節點被判斷客觀下線以後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操作。

 

監視該主節點的所有哨兵都有可能被選爲領導者,選舉使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一輪選舉中,哨兵A向B發送成爲領導者的申請,如果B沒有同意過其他哨兵,則會同意A成爲領導者。選舉的具體過程這裏不做詳細描述,一般來說,哨兵選擇的過程很快,誰先完成客觀下線,一般就能成爲領導者。

 

故障轉移:選舉出的領導者哨兵,開始進行故障轉移操作,該操作大體可以分爲3個步驟:

 

  • 在從節點中選擇新的主節點:選擇的原則是,首先過濾掉不健康的從節點;然後選擇優先級最高的從節點(由slave-priority指定);如果優先級無法區分,則選擇複製偏移量最大的從節點;如果仍無法區分,則選擇runid最小的從節點。

  • 更新主從狀態:通過slaveof no one命令,讓選出來的從節點成爲主節點;並通過slaveof命令讓其他節點成爲其從節點。

  • 將已經下線的主節點(即6379)設置爲新的主節點的從節點,當6379重新上線後,它會成爲新的主節點的從節點。

 

通過上述幾個關鍵概念,可以基本瞭解哨兵的工作原理。爲了更形象的說明,下圖展示了領導者哨兵節點的日誌,包括從節點啓動到完成故障轉移。

 

 

五、配置與實踐建議

 

1.配置

 

下面介紹與哨兵相關的幾個配置。

 

配置1:sentinel monitor {masterName} {masterIp} {masterPort} {quorum}

 

sentinel monitor是哨兵最核心的配置,在前文講述部署哨兵節點時已說明,其中:masterName指定了主節點名稱,masterIp和masterPort指定了主節點地址,quorum是判斷主節點客觀下線的哨兵數量閾值:當判定主節點下線的哨兵數量達到quorum時,對主節點進行客觀下線。建議取值爲哨兵數量的一半加1。

 

配置2:sentinel down-after-milliseconds {masterName} {time}

 

sentinel down-after-milliseconds與主觀下線的判斷有關:哨兵使用ping命令對其他節點進行心跳檢測,如果其他節點超過down-after-milliseconds配置的時間沒有回覆,哨兵就會將其進行主觀下線。該配置對主節點、從節點和哨兵節點的主觀下線判定都有效。

 

down-after-milliseconds的默認值是30000,即30s;可以根據不同的網絡環境和應用要求來調整:值越大,對主觀下線的判定會越寬鬆,好處是誤判的可能性小,壞處是故障發現和故障轉移的時間變長,客戶端等待的時間也會變長。例如,如果應用對可用性要求較高,則可以將值適當調小,當故障發生時儘快完成轉移;如果網絡環境相對較差,可以適當提高該閾值,避免頻繁誤判。

 

配置3:sentinel parallel - syncs {masterName} {number}

 

sentinel parallel-syncs與故障轉移之後從節點的複製有關:它規定了每次向新的主節點發起復制操作的從節點個數。例如,假設主節點切換完成之後,有3個從節點要向新的主節點發起復制;如果parallel-syncs=1,則從節點會一個一個開始複製;如果parallel-syncs=3,則3個從節點會一起開始複製。

 

parallel-syncs取值越大,從節點完成複製的時間越快,但是對主節點的網絡負載、硬盤負載造成的壓力也越大;應根據實際情況設置。例如,如果主節點的負載較低,而從節點對服務可用的要求較高,可以適量增加parallel-syncs取值。parallel-syncs的默認值是1。

 

配置4:sentinel failover - timeout {masterName} {time}

 

sentinel failover-timeout與故障轉移超時的判斷有關,但是該參數不是用來判斷整個故障轉移階段的超時,而是其幾個子階段的超時,例如如果主節點晉升從節點時間超過timeout,或從節點向新的主節點發起復制操作的時間(不包括複製數據的時間)超過timeout,都會導致故障轉移超時失敗。

 

failover-timeout的默認值是180000,即180s;如果超時,則下一次該值會變爲原來的2倍。

 

配置5:除上述幾個參數外,還有一些其他參數,如安全驗證相關的參數,這裏不做介紹。

 

2.實踐建議

 

  • 哨兵節點的數量應不止一個。一方面增加哨兵節點的冗餘,避免哨兵本身成爲高可用的瓶頸;另一方面減少對下線的誤判。此外,這些不同的哨兵節點應部署在不同的物理機上。

  • 哨兵節點的數量應該是奇數,便於哨兵通過投票做出“決策”:領導者選舉的決策、客觀下線的決策等。

  • 各個哨兵節點的配置應一致,包括硬件、參數等;此外,所有節點都應該使用ntp或類似服務,保證時間準確、一致。

  • 哨兵的配置提供者和通知客戶端功能,需要客戶端的支持才能實現,如前文所說的Jedis;如果開發者使用的庫未提供相應支持,則可能需要開發者自己實現。

  • 當哨兵系統中的節點在Docker(或其他可能進行端口映射的軟件)中部署時,應特別注意端口映射可能會導致哨兵系統無法正常工作,因爲哨兵的工作基於與其他節點的通信,而Docker的端口映射可能導致哨兵無法連接到其他節點。例如,哨兵之間互相發現,依賴於它們對外宣稱的IP和port,如果某個哨兵A部署在做了端口映射的Docker中,那麼其他哨兵使用A宣稱的port無法連接到A。

 

六、總結

 

本文首先介紹了哨兵的作用:監控、故障轉移、配置提供者和通知;然後講述了哨兵系統的部署方法,以及通過客戶端訪問哨兵系統的方法;再然後簡要說明了哨兵實現的基本原理;最後給出了關於哨兵實踐的一些建議。

 

在主從複製的基礎上,哨兵引入了主節點的自動故障轉移,進一步提高了Redis的高可用性;但是哨兵的缺陷同樣很明顯:哨兵無法對從節點進行自動故障轉移,在讀寫分離場景下,從節點故障會導致讀服務不可用,需要我們對從節點做額外的監控、切換操作。

 

此外,哨兵仍然沒有解決寫操作無法負載均衡、及存儲能力受到單機限制的問題;這些問題的解決需要使用集羣,歡迎關注社羣后續內容。

七、集羣

1. Redis集羣方案

Redis Cluster 集羣模式通常具有 高可用可擴展性分佈式容錯 等特性。Redis 分佈式方案一般有兩種:

1.1 客戶端分區方案

客戶端 就已經決定數據會被 存儲 到哪個 redis 節點或者從哪個 redis 節點 讀取數據。其主要思想是採用 哈希算法 將 Redis 數據的 key 進行散列,通過 hash 函數,特定的 key會 映射 到特定的 Redis 節點上。

客戶端分區方案 的代表爲 Redis Sharding,Redis Sharding 是 Redis Cluster 出來之前,業界普遍使用的 Redis 多實例集羣 方法。Java 的 Redis 客戶端驅動庫 Jedis,支持 Redis Sharding 功能,即 ShardedJedis 以及 結合緩存池 的 ShardedJedisPool。

優點

不使用 第三方中間件分區邏輯 可控,配置 簡單,節點之間無關聯,容易 線性擴展,靈活性強。

缺點

客戶端 無法 動態增刪 服務節點,客戶端需要自行維護 分發邏輯,客戶端之間 無連接共享,會造成 連接浪費

1.2. 代理分區方案

客戶端 發送請求到一個 代理組件代理 解析 客戶端 的數據,並將請求轉發至正確的節點,最後將結果回覆給客戶端。

優點:簡化 客戶端 的分佈式邏輯,客戶端 透明接入,切換成本低,代理的 轉發 和 存儲 分離。

缺點:多了一層 代理層,加重了 架構部署複雜度 和 性能損耗

代理分區 主流實現的有方案有 Twemproxy 和 Codis。

1.2.1. Twemproxy

Twemproxy 也叫 nutcraker,是 twitter 開源的一個 redis 和 memcache 的 中間代理服務器 程序。Twemproxy 作爲 代理,可接受來自多個程序的訪問,按照 路由規則,轉發給後臺的各個 Redis 服務器,再原路返回。Twemproxy 存在 單點故障 問題,需要結合 Lvs 和 Keepalived 做 高可用方案

優點:應用範圍廣,穩定性較高,中間代理層 高可用

缺點:無法平滑地 水平擴容/縮容,無 可視化管理界面,運維不友好,出現故障,不能 自動轉移

1.2.2. Codis

Codis 是一個 分佈式 Redis 解決方案,對於上層應用來說,連接 Codis-Proxy 和直接連接 原生的 Redis-Server 沒有的區別。Codis 底層會 處理請求的轉發,不停機的進行 數據遷移 等工作。Codis 採用了無狀態的 代理層,對於 客戶端 來說,一切都是透明的。

優點

實現了上層 Proxy 和底層 Redis 的 高可用數據分片 和 自動平衡,提供 命令行接口 和 RESTful API,提供 監控 和 管理 界面,可以動態 添加 和 刪除 Redis 節點。

缺點

部署架構 和 配置 複雜,不支持 跨機房 和 多租戶,不支持 鑑權管理

1.3. 查詢路由方案

客戶端隨機地 請求任意一個 Redis 實例,然後由 Redis 將請求 轉發 給 正確 的 Redis 節點。Redis Cluster 實現了一種 混合形式 的 查詢路由,但並不是 直接 將請求從一個 Redis 節點 轉發 到另一個 Redis 節點,而是在 客戶端 的幫助下直接 重定向( redirected)到正確的 Redis 節點。

優點

無中心節點,數據按照  存儲分佈在多個 Redis 實例上,可以平滑的進行節點 擴容/縮容,支持 高可用 和 自動故障轉移,運維成本低。

缺點

嚴重依賴 Redis-trib 工具,缺乏 監控管理,需要依賴 Smart Client (維護連接緩存路由表,MultiOp 和 Pipeline 支持)。Failover 節點的 檢測過慢,不如 中心節點 ZooKeeper 及時。Gossip 消息具有一定開銷。無法根據統計區分 冷熱數據

2. 數據分佈

2.1. 數據分佈理論

分佈式數據庫 首先要解決把 整個數據集 按照 分區規則 映射到 多個節點 的問題,即把 數據集 劃分到 多個節點 上,每個節點負責 整體數據 的一個 子集

數據分佈通常有 哈希分區 和 順序分區 兩種方式,對比如下:

分區方式 特點 相關產品 哈希分區 離散程度好,數據分佈與業務無關,無法順序訪問 Redis Cluster,Cassandra,Dynamo 順序分區 離散程度易傾斜,數據分佈與業務相關,可以順序訪問 BigTable,HBase,Hypertable 由於 Redis Cluster 採用 哈希分區規則,這裏重點討論 哈希分區。常見的 哈希分區 規則有幾種,下面分別介紹:

2.1.1. 節點取餘分區

使用特定的數據,如 Redis 的  或 用戶 ID,再根據 節點數量 N 使用公式:hash(key)% N 計算出 哈希值,用來決定數據 映射 到哪一個節點上。

優點

這種方式的突出優點是 簡單性,常用於 數據庫 的 分庫分表規則。一般採用 預分區 的方式,提前根據 數據量 規劃好 分區數,比如劃分爲 512 或 1024 張表,保證可支撐未來一段時間的 數據容量,再根據 負載情況 將  遷移到其他 數據庫 中。擴容時通常採用 翻倍擴容,避免 數據映射 全部被 打亂,導致 全量遷移 的情況。

缺點

當 節點數量 變化時,如 擴容 或 收縮 節點,數據節點 映射關係 需要重新計算,會導致數據的 重新遷移

2.1.2. 一致性哈希分區

一致性哈希 可以很好的解決 穩定性問題,可以將所有的 存儲節點 排列在 收尾相接 的 Hash 環上,每個 key 在計算 Hash 後會 順時針 找到 臨接 的 存儲節點 存放。而當有節點 加入 或 退出 時,僅影響該節點在 Hash 環上 順時針相鄰 的 後續節點

優點

加入 和 刪除 節點隻影響 哈希環 中 順時針方向 的 相鄰的節點,對其他節點無影響。

缺點

加減節點 會造成 哈希環 中部分數據 無法命中。當使用 少量節點 時,節點變化 將大範圍影響 哈希環 中 數據映射,不適合 少量數據節點 的分佈式方案。普通 的 一致性哈希分區 在增減節點時需要 增加一倍 或 減去一半 節點才能保證 數據 和 負載的均衡

注意:因爲 一致性哈希分區 的這些缺點,一些分佈式系統採用 虛擬槽 對 一致性哈希 進行改進,比如 Dynamo 系統。

2.1.3. 虛擬槽分區

虛擬槽分區 巧妙地使用了 哈希空間,使用 分散度良好 的 哈希函數 把所有數據 映射 到一個 固定範圍 的 整數集合 中,整數定義爲 (slot)。這個範圍一般 遠遠大於 節點數,比如 Redis Cluster 槽範圍是 0 ~ 16383。 是集羣內 數據管理 和 遷移 的 基本單位。採用 大範圍槽 的主要目的是爲了方便 數據拆分 和 集羣擴展。每個節點會負責 一定數量的槽,如圖所示:

當前集羣有 5 個節點,每個節點平均大約負責 3276 個 。由於採用 高質量 的 哈希算法,每個槽所映射的數據通常比較 均勻,將數據平均劃分到 5 個節點進行 數據分區。Redis Cluster 就是採用 虛擬槽分區

節點1: 包含 0 到 3276 號哈希槽。

節點2:包含 3277 到 6553 號哈希槽。

節點3:包含 6554 到 9830 號哈希槽。

節點4:包含 9831 到 13107 號哈希槽。

節點5:包含 13108 到 16383 號哈希槽。

這種結構很容易 添加 或者 刪除 節點。如果 增加 一個節點 6,就需要從節點 1 ~ 5 獲得部分  分配到節點 6 上。如果想 移除 節點 1,需要將節點 1 中的  移到節點 2 ~ 5 上,然後將 沒有任何槽 的節點 1 從集羣中 移除 即可。

由於從一個節點將 哈希槽 移動到另一個節點並不會 停止服務,所以無論 添加刪除或者 改變 某個節點的 哈希槽的數量 都不會造成 集羣不可用 的狀態.

2.2. Redis的數據分區

Redis Cluster 採用 虛擬槽分區,所有的  根據 哈希函數 映射到 0~16383 整數槽內,計算公式:slot = CRC16(key)& 16383。每個節點負責維護一部分槽以及槽所映射的 鍵值數據,如圖所示:

2.2.1. Redis虛擬槽分區的特點

解耦 數據 和 節點 之間的關係,簡化了節點 擴容 和 收縮 難度。

節點自身 維護槽的 映射關係,不需要 客戶端 或者 代理服務 維護 槽分區元數據

支持 節點 之間的 映射查詢,用於 數據路由在線伸縮 等場景。

2.3. Redis集羣的功能限制

Redis 集羣相對 單機 在功能上存在一些限制,需要 開發人員 提前瞭解,在使用時做好規避。

key 批量操作 支持有限。

類似 mset、mget 操作,目前只支持對具有相同 slot 值的 key 執行 批量操作。對於 映射爲不同 slot 值的 key 由於執行 mget、mget 等操作可能存在於多個節點上,因此不被支持。

key 事務操作 支持有限。

只支持  key 在 同一節點上 的 事務操作,當多個 key 分佈在 不同 的節點上時 無法 使用事務功能。

key 作爲 數據分區 的最小粒度

不能將一個 大的鍵值 對象如 hash、list 等映射到 不同的節點

不支持 多數據庫空間

單機 下的 Redis 可以支持 16 個數據庫(db0 ~ db15),集羣模式 下只能使用 一個 數據庫空間,即 db0。

複製結構 只支持一層

從節點 只能複製 主節點,不支持 嵌套樹狀複製 結構。

3. Redis集羣搭建

Redis-Cluster 是 Redis 官方的一個 高可用 解決方案,Cluster 中的 Redis 共有 2^14(16384) 個 slot 。創建 Cluster 後, 會 平均分配 到每個 Redis 節點上。

下面介紹一下本機啓動 6 個 Redis 的 集羣服務,並使用 redis-trib.rb 創建 3主3從 的 集羣。搭建集羣工作需要以下三個步驟:

3.1. 準備節點

Redis 集羣一般由 多個節點 組成,節點數量至少爲 6 個,才能保證組成 完整高可用 的集羣。每個節點需要 開啓配置 cluster-enabled yes,讓 Redis 運行在 集羣模式 下。

Redis 集羣的節點規劃如下:

節點名稱 端口號 是主是從 所屬主節點 redis-6379 6379 主節點 --- redis-6389 6389 從節點 redis-6379 redis-6380 6380 主節點 --- redis-6390 6390 從節點 redis-6380 redis-6381 6381 主節點 --- redis-6391 6391 從節點 redis-6381 注意:建議爲集羣內 所有節點 統一目錄,一般劃分三個目錄:conf、data、log,分別存放 配置數據和 日誌 相關文件。把 6 個節點配置統一放在 conf 目錄下。

3.1.1. 創建redis各實例目錄

$sudo mkdir -p /usr/local/redis-cluster$cd/usr/local/redis-cluster$sudo mkdir conf datalog$ sudo mkdir -p data/redis-6379 data/redis-6389 data/redis-6380 data/redis-6390 data/redis-6381 data/redis-6391複製代碼

3.1.2. redis配置文件管理

根據以下 模板 配置各個實例的 redis.conf,以下只是搭建集羣需要的 基本配置,可能需要根據實際情況做修改。

# redis後臺運行daemonizeyes# 綁定的主機端口bind 127.0.0.1# 數據存放目錄dir /usr/local/redis-cluster/data/redis-6379# 進程文件pidfile /var/run/redis-cluster/${自定義}.pid# 日誌文件logfile /usr/local/redis-cluster/log/${自定義}.log# 端口號port 6379# 開啓集羣模式,把註釋#去掉cluster-enabledyes# 集羣的配置,配置文件首次啓動自動生成cluster-config-file /usr/local/redis-cluster/conf/${自定義}.conf# 請求超時,設置10秒cluster-node-timeout 10000# aof日誌開啓,有需要就開啓,它會每次寫操作都記錄一條日誌appendonlyyes複製代碼

redis-6379.conf

daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6379pidfile /var/run/redis-cluster/redis-6379.pidlogfile /usr/local/redis-cluster/log/redis-6379.logport 6379cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6379.confcluster-node-timeout 10000appendonly yes複製代碼

redis-6389.conf

daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6389pidfile /var/run/redis-cluster/redis-6389.pidlogfile /usr/local/redis-cluster/log/redis-6389.logport 6389cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6389.confcluster-node-timeout 10000appendonly yes複製代碼

redis-6380.conf

daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6380pidfile /var/run/redis-cluster/redis-6380.pidlogfile /usr/local/redis-cluster/log/redis-6380.logport 6380cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6380.confcluster-node-timeout 10000appendonly yes複製代碼

redis-6390.conf

daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6390pidfile /var/run/redis-cluster/redis-6390.pidlogfile /usr/local/redis-cluster/log/redis-6390.logport 6390cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6390.confcluster-node-timeout 10000appendonly yes複製代碼

redis-6381.conf

daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6381pidfile /var/run/redis-cluster/redis-6381.pidlogfile /usr/local/redis-cluster/log/redis-6381.logport 6381cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6381.confcluster-node-timeout 10000appendonly yes複製代碼

redis-6391.conf

daemonize yesbind 127.0.0.1dir/usr/local/redis-cluster/data/redis-6391pidfile /var/run/redis-cluster/redis-6391.pidlogfile /usr/local/redis-cluster/log/redis-6391.logport 6391cluster-enabled yescluster-config-file/usr/local/redis-cluster/conf/node-6391.confcluster-node-timeout 10000appendonly yes複製代碼

3.2. 環境準備

3.2.1. 安裝Ruby環境

$ sudobrew install ruby複製代碼

3.2.2. 準備rubygem redis依賴

$ sudo geminstallredisPassword:Fetching: redis-4.0.2.gem (100%)Successfully installed redis-4.0.2Parsing documentationforredis-4.0.2Installing ri documentationforredis-4.0.2Done installing documentationforredisafter1seconds1gem installed複製代碼

3.2.3. 拷貝redis-trib.rb到集羣根目錄

redis-trib.rb 是 redis 官方推出的管理 redis 集羣 的工具,集成在 redis 的源碼 src 目錄下,將基於 redis 提供的 集羣命令 封裝成 簡單便捷實用 的 操作工具

$ sudo cp/usr/local/redis-4.0.11/src/redis-trib.rb /usr/local/redis-cluster複製代碼

查看 redis-trib.rb 命令環境是否正確,輸出如下:

$ ./redis-trib.rb Usage:redis-trib create host1:port1 ...hostN:portN--replicascheck host:portinfo host:portfix host:port--timeoutreshard host:port--from--to--slots--yes--timeout--pipelinerebalance host:port--weight--auto-weights--use-empty-masters--timeout--simulate--pipeline--thresholdadd-node new_host:new_port existing_host:existing_port--slave--master-iddel-node host:port node_idset-timeouthost:port millisecondscall host:port command arg arg ..argimport host:port--from--copy--replacehelp (show this help)For check,fix,reshard,del-node,set-timeoutyou can specify the host and port of any working node in the cluster.複製代碼

redis-trib.rb 是 redis 作者用 ruby 完成的。redis-trib.rb 命令行工具 的具體功能如下:

命令 作用 create 創建集羣 check 檢查集羣 info 查看集羣信息 fix 修復集羣 reshard 在線遷移slot rebalance 平衡集羣節點slot數量 add-node 將新節點加入集羣 del-node 從集羣中刪除節點 set-timeout 設置集羣節點間心跳連接的超時時間 call 在集羣全部節點上執行命令 import 將外部redis數據導入集羣 3.3. 安裝集羣

3.3.1. 啓動redis服務節點

運行如下命令啓動 6 臺 redis 節點:

sudo redis-serverconf/redis-6379.confsudo redis-serverconf/redis-6389.confsudo redis-serverconf/redis-6380.confsudo redis-serverconf/redis-6390.confsudo redis-serverconf/redis-6381.confsudo redis-serverconf/redis-6391.conf複製代碼

啓動完成後,redis 以集羣模式啓動,查看各個 redis 節點的進程狀態:

$ ps -ef | grep redis-server 0 1908 1 0 4:59下午 ?? 0:00.01 redis-server *:6379 [cluster] 0 1911 1 0 4:59下午 ?? 0:00.01 redis-server *:6389 [cluster] 0 1914 1 0 4:59下午 ?? 0:00.01 redis-server *:6380 [cluster] 0 1917 1 0 4:59下午 ?? 0:00.01 redis-server *:6390 [cluster] 0 1920 1 0 4:59下午 ?? 0:00.01 redis-server *:6381 [cluster] 0 1923 1 0 4:59下午 ?? 0:00.01 redis-server *:6391 [cluster] 複製代碼

在每個 redis 節點的 redis.conf 文件中,我們都配置了 cluster-config-file 的文件路徑,集羣啓動時,conf 目錄會新生成 集羣 節點配置文件。查看文件列表如下:

$ tree -L3..├── appendonly.aof├── conf│ ├── node-6379.conf│ ├── node-6380.conf│ ├── node-6381.conf│ ├── node-6389.conf│ ├── node-6390.conf│ ├── node-6391.conf│ ├── redis-6379.conf│ ├── redis-6380.conf│ ├── redis-6381.conf│ ├── redis-6389.conf│ ├── redis-6390.conf│ └── redis-6391.conf├── data│ ├── redis-6379│ ├── redis-6380│ ├── redis-6381│ ├── redis-6389│ ├── redis-6390│ └── redis-6391├── log│ ├── redis-6379.log│ ├── redis-6380.log│ ├── redis-6381.log│ ├── redis-6389.log│ ├── redis-6390.log│ └── redis-6391.log└── redis-trib.rb9directories,20files複製代碼

3.3.2. redis-trib關聯集羣節點

按照 從主到從 的方式 從左到右 依次排列 6 個 redis 節點。

$ sudo ./redis-trib.rb create --replicas1127.0.0.1:6379127.0.0.1:6380127.0.0.1:6381127.0.0.1:6389127.0.0.1:6390127.0.0.1:6391複製代碼

集羣創建後,redis-trib 會先將 16384 個 哈希槽 分配到 3 個 主節點,即 redis-6379,redis-6380 和 redis-6381。然後將各個 從節點 指向 主節點,進行 數據同步

>>>Creatingcluster>>>Performinghashslotsallocationon6nodes...Using3masters:127.0.0.1:6379127.0.0.1:6380127.0.0.1:6381Addingreplica127.0.0.1:6390to127.0.0.1:6379Addingreplica127.0.0.1:6391to127.0.0.1:6380Addingreplica127.0.0.1:6389to127.0.0.1:6381>>>Tryingtooptimizeslavesallocationforanti-affinity[WARNING]SomeslavesareinthesamehostastheirmasterM:ad4b9ffceba062492ed67ab336657426f55874b7127.0.0.1:6379slots:0-5460(5461slots)masterM:df23c6cad0654ba83f0422e352a81ecee822702e127.0.0.1:6380slots:5461-10922(5462slots)masterM:ab9da92d37125f24fe60f1f33688b4f8644612ee127.0.0.1:6381slots:10923-16383(5461slots)masterS: 25cfa11a2b4666021da5380ff332b80dbda97208127.0.0.1:6389replicatesad4b9ffceba062492ed67ab336657426f55874b7S: 48e0a4b539867e01c66172415d94d748933be173127.0.0.1:6390replicatesdf23c6cad0654ba83f0422e352a81ecee822702eS:d881142a8307f89ba51835734b27cb309a0fe855127.0.0.1:6391replicatesab9da92d37125f24fe60f1f33688b4f8644612ee複製代碼

然後輸入 yes,redis-trib.rb 開始執行 節點握手 和 槽分配 操作,輸出如下:

CanIsettheaboveconfiguration? (type'yes'to accept):yes>>>Nodesconfigurationupdated>>>Assignadifferentconfigepochtoeachnode>>>SendingCLUSTERMEETmessagestojointheclusterWaitingfortheclustertojoin....>>>PerformingClusterCheck(using node127.0.0.1:6379)M:ad4b9ffceba062492ed67ab336657426f55874b7127.0.0.1:6379slots:0-5460(5461slots)master1additionalreplica(s)M:ab9da92d37125f24fe60f1f33688b4f8644612ee127.0.0.1:6381slots:10923-16383(5461slots)master1additionalreplica(s)S:48e0a4b539867e01c66172415d94d748933be173127.0.0.1:6390slots: (0slots)slavereplicatesdf23c6cad0654ba83f0422e352a81ecee822702eS:d881142a8307f89ba51835734b27cb309a0fe855127.0.0.1:6391slots: (0slots)slavereplicatesab9da92d37125f24fe60f1f33688b4f8644612eeM:df23c6cad0654ba83f0422e352a81ecee822702e127.0.0.1:6380slots:5461-10922(5462slots)master1additionalreplica(s)S:25cfa11a2b4666021da5380ff332b80dbda97208127.0.0.1:6389slots: (0slots)slavereplicatesad4b9ffceba062492ed67ab336657426f55874b7[OK]Allnodesagreeaboutslotsconfiguration.>>>Checkforopenslots...>>>Checkslotscoverage...[OK]All16384slotscovered.複製代碼

執行 集羣檢查,檢查各個 redis 節點佔用的 哈希槽(slot)的個數以及 slot 覆蓋率。16384 個槽位中,主節點 redis-6379、redis-6380 和 redis-6381 分別佔用了 5461、5461 和 5462 個槽位。

3.3.3. redis主節點的日誌

可以發現,通過 BGSAVE 命令,從節點 redis-6389 在 後臺 異步地從 主節點 redis-6379 同步數據。

$ catlog/redis-6379.log1907:C05Sep16:59:52.960# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo1907:C05Sep16:59:52.961# Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=1907, just started1907:C05Sep16:59:52.961# Configuration loaded1908:M05Sep16:59:52.964* Increased maximumnumberofopen filesto10032(itwas originallysetto256).1908:M05Sep16:59:52.965* No cluster configuration found, I'm ad4b9ffceba062492ed67ab336657426f55874b71908:M05Sep16:59:52.967* Running mode=cluster, port=6379.1908:M05Sep16:59:52.967# Server initialized1908:M05Sep16:59:52.967* Readytoaccept connections1908:M05Sep17:01:17.782# configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH1908:M05Sep17:01:17.812# IP address for this node updated to 127.0.0.11908:M05Sep17:01:22.740# Cluster state changed: ok1908:M05Sep17:01:23.681* Slave127.0.0.1:6389asksforsynchronization1908:M05Sep17:01:23.681* Partial resynchronizationnotaccepted: Replication ID mismatch (Slave askedfor'4c5afe96cac51cde56039f96383ea7217ef2af41',myreplication IDs are '037b661bf48c80c577d1fa937ba55367a3692921'and'0000000000000000000000000000000000000000')1908:M05Sep17:01:23.681* Starting BGSAVEforSYNCwithtarget: disk1908:M05Sep17:01:23.682* Background saving startedbypid19521952:C05Sep17:01:23.683* DB savedondisk1908:M05Sep17:01:23.749* Background saving terminatedwithsuccess1908:M05Sep17:01:23.752* Synchronizationwithslave127.0.0.1:6389succeeded複製代碼

 

 

參考文件:

https://www.jianshu.com/p/ba3cc187da9c

https://www.cnblogs.com/wade-luffy/p/9639986.html

http://www.redis.cn/articles/20181020001.html

https://www.jianshu.com/p/84dbb25cc8dc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章