redis 集羣(文檔整理)

Redis集羣

·Redis集羣提供了一種運行Redis安裝的方法,在該安裝中,數據會在多個Redis節點之間自動分片。css

Redis集羣在分區期間還提供了必定程度的可用性,這其實是在某些節點出現故障或沒法通訊時有繼續工做的能力。可是,若是發生較嚴重故障(例如,大多數主節點不可用時),集羣將中止運行。node

實際上,Redis集羣能給你帶來什麼?git

  • 自動在多個節點之間拆分數據集的能力。
  • 當一部分節點出現故障或沒法與集羣中其餘節點通訊時,仍然能夠繼續操做。

Redis集羣TCP端口

每一個Redis集羣節點都須要打開兩個TCP鏈接。用於服務客戶端的常規RedisTCP端口,例如6379,再加上將數據端口加10000的端口,好比在示例中爲16379github

第二個值更大一點的端口用於集羣總線,也就是使用二進制協議的節點到節點之間的通訊通道。節點將集羣總線用於故障檢測,配置更新,故障轉移受權等。客戶端永遠不要嘗試與集羣總線端口進行通訊,而應始終與普通的Redis命令端口進行通訊,可是請確保您在防火牆中同時打開了這兩個端口,不然Redis集羣節點將沒法進行通訊。redis

命令端口和集羣總線端口的偏移量是固定的,而且始終爲10000數據庫

請注意,對於每一個節點,要使Redis集羣正常工做,您須要:swift

  1. 用於與客戶端通訊的常規通訊端口(一般爲6379),向那些須要訪問集羣的全部客戶端以及全部其餘集羣節點(使用客戶端端口進行key遷移)開放。
  2. 集羣總線端口(客戶端端口+ 10000)必須能夠從全部其餘集羣節點訪問。

若是您沒有同時打開兩個TCP端口,則集羣將沒法正常工做。promise

集羣總線使用不一樣的二進制協議進行節點到節點的數據交換,它更適合於在節點之間使用較少的帶寬和較少的處理時間來交換信息。緩存

Redis集羣數據分片

Redis集羣不使用一致性哈希,而是使用一種不一樣形式的分片,從概念上講每一個key都是咱們稱爲hash槽的一部分。安全

Redis集羣中有16384個hash槽,要計算給定key的hash槽,需將key的CRC16值用16384取模。

Redis集羣中的每一個節點都負責hash槽的子集,例如,您可能有一個包含3個節點的集羣,其中:

  • 節點A包含從0到5500的hash槽。
  • 節點B包含從5501到11000的hash槽。
  • 節點C包含從11001到16383的hash槽。

這樣能夠輕鬆添加和刪除集羣中的節點。例如,若是我想添加一個新節點D,則須要將一些hash槽從節點A,B,C移到D。相似地,若是我想從集羣中刪除節點A,則只需移動A所服務的hash槽到B和C。當節點A爲空時,我能夠將其從集羣中徹底刪除。

由於將hash槽從一個節點移動到另外一個節點不須要中止操做,因此添加刪除節點或更改節點服務的hash槽的百分比不須要停機。

只要單個命令執行(或整個事務或Lua腳本執行)中涉及的全部key都屬於同一個hash槽,Redis集羣就支持多key操做。用戶能夠經過使用稱爲hash標籤的概念來強制多個key成爲同一hash槽的一部分。

hash標籤記錄在Redis集羣規範中,注意,若是key的{}中的括號之間有一個子字符串,則僅對字符串中的內容進行hash處理,例如,一個叫{foo}的key和另外一個叫{foo} 的 key保證在同一hash槽中,而且能夠在以多個key做爲參數的命令中一塊兒使用。

Redis集羣主備模式

爲了在主節點子集發生故障或沒法與大多數節點通訊時保持可用,Redis集羣使用主備模型,其中每一個hash槽具備從1(主節點自己)到N個副本(N -1個其餘備份節點)。

在一個包含節點A,B,C的集羣中,若是節點B失敗,則集羣將沒法繼續,由於咱們不能爲 5501-11000 範圍內的hash槽提供服務。可是,在建立集羣(或稍後)時,咱們向每一個主節點添加一個備份節點,以便最終集羣由做爲主節點的A,B,C和做爲備份節點的A1,B1,C1組成 ,若是節點B發生故障,系統將可以持續運行。節點B1複製B,而且B發生故障,集羣會將節點B1提高爲新的主節點,並將繼續正常運行。

可是請注意,若是節點B和B1同時失敗,則Redis集羣沒法繼續運行。

Redis集羣的一致性保證

Redis集羣沒法保證強一致性。 實際上,這意味着在某些狀況下,Redis集羣可能會丟失系統給客戶端的已經確認的寫操做。

Redis集羣可能丟失寫入的第一個緣由是由於它使用異步複製。這意味着在寫入期
間會發生如下狀況:

  • 您的客戶端向B主節點寫入數據。
  • B主節點向您的客戶端答覆「肯定」。
  • B主節點將寫操做傳播到其備份節點B1,B2和B3。

如您所見,B在回覆客戶端以前不會等待B1,B2,B3的確認,由於這會對Redis形成延遲,所以,若是您的客戶端進行了寫操做,而後B會確認,可是在它把寫操做發送給備份節點以前崩潰了,此時其中一個備份節點(未接收到寫操做)能夠升級爲主節點,這樣就永遠丟失該寫操做。這與配置爲每秒將數據刷新到磁盤的大多數數據庫所發生的狀況很是類似,所以因爲過去使用不涉及分佈式系統的傳統數據庫系統的經驗,您已經能夠對此進行合理推斷。一樣,您能夠經過強制數據庫在答覆客戶端以前刷新磁盤上的數據來提升一致性,但這一般會致使性能太低。在Redis集羣下,這至關於同步複製。

基本上,在性能和一致性之間進行權衡是必須的。

Redis集羣在須要時能夠經過WAIT命令實現同步寫,這使得丟失寫的可能性大大下降,可是請注意,即便使用同步複製,Redis集羣也不實現強一致性:在更復雜的狀況下,老是有可能存在一種場景,就是一個沒法接收數據的備份節點被選爲主節點。

還有一種值得注意的狀況,Redis集羣也會丟失寫操做,這種狀況發生在網絡分區期間,在該分區中,客戶端與少數實例(至少包括主節點)隔離。

以咱們的6個節點集羣爲例,該集羣由A,B,C,A1,B1,C1組成,具備3個主節點
和3個備份節點。還有一個客戶,咱們將其稱爲Z1。

發生分區後,可能在分區的一側有A,C,A1,B1,C1,而在另外一側有B和Z1。

Z1仍然可以對B進行寫操做,B將接受其寫入。若是分區在很短的時間內恢復正常,則集羣將繼續正常運行。可是,若是分區持續的時間足以使B1升級爲該分區的多數端的主節點,則Z1向B發送的寫操做將丟失。

請注意,Z1將可以發送到B的寫入量有一個最大的窗口:若是已經有足夠的時間使大分區選舉出一個主節點,則小分區中的每一個主節點都將中止接受寫入。該時間是Redis集羣的一個很是重要的配置指令,稱爲節點超時。

在節點超時以後,主節點被視爲發生故障,而且能夠用其副本之一替換。相似地,在超過指定的時間後,主節點仍是沒法感知大多數其餘主節點,此主節點進入錯誤狀態並中止接受寫入。

Redis集羣配置參數

咱們將建立一個集羣部署做爲例子。在繼續以前,讓咱們介紹一下Redis集羣裏的redis.conf文件中引入的配置參數。
繼續閱讀下去您就會得到更多清晰的要點。

  • cluster-enabled <yes/no> : 若是設置爲yes,Redis實例中將會啓用集羣支持。不然,該實例將像往常同樣做爲獨立實例啓動。
  • cluster-config-file : 請注意,儘管有此選項,但它是不容許用戶可編輯的配置文件,而是Redis集
    羣節點在每次有變動時(基本上是狀態)都會自動持久保存的集羣配置文件,以便可以在啓動時從新讀取它。
    該文件列出了諸如集羣中其餘節點的內容,狀態,持久變量等等之類的東西。一般,在收到某些消息時,此文件將被
    重寫並刷新到磁盤上。
  • cluster-node-timeout <毫秒> :Redis集羣節點在被認爲故障前的最長間隔時間。若是沒法訪問主節點的時間超過指定的時間長度,則它的備份節點將對
    其進行故障轉移。此參數也控制Redis集羣中的其餘重要事情。值得注意的是,在指定的時間內沒法連通大多數主節點
    的每一個節點都將中止接受查詢請求。
  • cluster-slave-validity-factor :若是設置爲零,則備份節點將始終嘗試對主節點進行故障轉移,而無論主節點和備份節點之間的連接斷開
    狀態的時間長短。若是該值爲正,則將最大斷開時間計算爲節點超時時間乘以此選項提供的因子,若是該節點是備份
    節點,而且主連接斷開的時間超過了指定的時間,它將不會嘗試啓動故障轉移。例如,若是節點超時設置爲5秒,而有
    效性因子設置爲10,則備份節點與主節點斷開鏈接超過50秒將不會嘗試對其主節點進行故障轉移。請注意,若是沒有
    備份節點能夠對其進行故障轉移,則任何不爲零的值均可能致使Redis集羣在主節點發生故障後不可用。
    在這種狀況下,只有當原始主節點從新加入集羣后,集羣纔會返回可用狀態。
  • cluster-migration-barrier :主節點須要保持鏈接的備份節點的最小數量,以便另外一個備份節點遷移到一個沒有任何備份節點覆蓋的主
    節點。有關更多信息,請參見本教程中有關副本遷移的相應部分。
  • cluster-require-full-coverage <yes / no>:若是設置爲yes,默認狀況下,若是某個節點未覆蓋必定比例的key空間,集羣將中止接受寫入。若是該選項設
    置爲no,即便此節點僅能處理有關key的部分子集的請求,集羣仍將提供查詢。

建立和使用一個Redis集羣

注意:手動部署Redis集羣,瞭解其某些操做很是重要。 可是,若是要儘快創建集羣並運行,請跳過本節和下一節,直接轉到使用 create-cluster 腳本建立Redis集羣。

要建立集羣,咱們須要作的第一件事就是讓一些空Redis實例運行在集羣模式下。基本上,這意味着不能使用常規Redis實例來建立集羣,由於須要配置特殊模式,以便Redis實例啓用集羣特定的功能和命令。

如下是最小的Redis集羣配置文件:

port 7000  # 端口號
cluster-enabled yes  # 是成爲cluster節點
cluster-config-file nodes.conf  # 節點配置文件
cluster-node-timeout 5000   # 節點超時時間
appendonly yes  # 是否使用aof

啓用集羣模式的只須要直接打開cluster-enabled命令。每一個實例還包含該節點配置存儲位置的文件路徑,默認狀況下爲nodes.conf。 該文件不會被人接觸。 它只是由Redis集羣實例在啓動時生成,並在須要時進行更新。

請注意,按預期工做的最小集羣要求至少包含三個主節點。 對於您的第一個測試,強烈建議啓動一個包含三個主節點和三個備份節點的六個節點集羣。爲此,輸入一個新目錄並建立如下目錄,該目錄以咱們將在給定目錄中運行的實例的端口號命名。就像是:

mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005

在從7000到7005的每一個目錄中建立一個redis.conf文件。做爲配置文件的模板,只需使用上面的小示例,但請確保根據目錄名稱用正確的端口號替換端口號7000。如今,將您的redis-server可執行文件(從GitHub不穩定分支中的最新資源編譯而來)複製到cluster-test目錄中,最後在您喜歡的終端應用程序中打開6個終端選項卡。

像這樣啓動每一個實例,每一個選項卡一個:

cd 7000
../redis-server ./redis.conf

從每一個實例的日誌中能夠看到,因爲不存在nodes.conf文件,所以每一個節點都會爲其分配一個新的ID。

[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1

該ID將由該實例永久使用,以使該實例在集羣的上下文中具備惟一的名稱。每一個節點都使用該ID而不是IP或端口記住其餘每一個節點。 IP地址和端口可能會更改,可是惟一的節點標識符在節點的整個生命週期中都不會改變。咱們將此標識符簡稱爲節點ID。

建立集羣

如今,咱們有許多實例正在運行,而後須要經過向節點寫入一些有意義的配置來建立集羣。

若是您使用的是Redis 5,這很容易完成,這是由於redis-cli中嵌入了Redis集羣命令行實用程序,咱們可使用它來建立新集羣,檢查或從新分片現有集羣等。

對於Redis版本3或4,有一個稱爲redis-trib.rb的較老的工具,它很是類似。您能夠在Redis源代碼分發的src目錄中找到它。 您須要安裝redis gem才能運行redis-trib。

gem install redis

第一個示例,即集羣建立,將在Redis 5中使用redis-cli以及在Redis 3和4中使用redis-trib來顯示。可是,接下來的全部示例都將僅使用redis-cli,由於您能夠看到他們語法很是類似,您也可使用redis-trib.rb help來獲取有關語法的信息,從而將一個命令行簡單地更改成另外一命令行。 重要:請注意,若是須要,能夠對Redis 4集羣使用Redis 5 redis-cli。

要使用redis-cli爲Redis 5建立集羣,只需鍵入:

redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1

對於redis 4或者3 請使用redis-trib.rb工具:

./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

此處使用的命令是create,由於咱們要建立一個新集羣。 選項
--cluster-replicas 1表示咱們但願每一個建立的主節點都具備一個備份節點。其餘參數是要用於建立新集羣的實例
的地址列表。

顯然,知足咱們要求的惟一設置是建立具備3個主節點和3個從節點的集羣。

Redis-cli將爲您提供配置。 鍵入yes,將接受建議的配置。集羣將被配置並加入,這意味着實例將被啓動而後彼此
之間能夠對話。 最後,若是一切順利,您將看到以下消息:

[OK] All 16384 slots covered

意思就是說至少有一個主節點實例服務於16384個槽位中某一個.

使用create-cluster腳本建立一個Redis集羣

若是您不想如上所述經過手動配置和執行單個實例來建立Redis集羣,則可使用更簡單的系統(可是您將不會學到一樣多的操做細節)。

只需檢查Redis發行版中的utils / create-cluster目錄。內部有一個名爲create-cluster的腳本(名稱與包含在其中的目錄相同),它是一個簡單的bash腳本。 爲了啓動具備3個主節點和3個備份節點的6節點集羣,只需鍵入如下命令:

  1. create-cluster start
  2. create-cluster create

在步驟2中,當redis-cli但願您接受集羣佈局時,回覆yes。

如今,您能夠與集羣進行交互,默認狀況下,第一個節點將從端口30001開始。 完成後,使用如下命令中止集羣:

create-cluster stop.

關於如何運行這個腳本的更多信息,請閱讀目錄裏的README。

集羣操做

到目前爲止,Redis集羣的問題之一是缺乏客戶端庫的實現。

據我所知有如下實現:

  • redis-rb-cluster 是我(@antirez)編寫的Ruby實現,可做爲其餘語言的參考。它是原始redis-rb的簡單包裝,實現了最小語義以有效地與集羣通訊。
  • redis-py-cluster redis-rb-cluster的Python實現。支持大多數redis-py功能。正在積極發展中。
  • 流行的Predis支持Redis集羣,該支持最近已更新而且正在積極開發中。
  • 使用最普遍的Java客戶端,Jedis最近添加了對Redis集羣的支持,請參閱項目README中的Jedis集羣部分。
  • StackExchange.Redis提供對C#的支持(而且應與大多數.NET語言,VB,F#等兼容)
  • thunk-redis提供對Node.js和io.js的支持,它是基於thunk/promise的redis客戶端,具備管道和集羣功能。
    redis-go-cluster是Go語言的
    Redis集羣的實現,它使用了Redigo library client做爲基本客戶端,經過結果聚合實現了MGET/MSET。
  • ioredis是流行的Node.js客戶端,爲Redis集羣提供了強大的支持。
  • 當使用-c開關啓動時,redis-cli程序實現了基本的集羣支持。

測試Redis集羣的一種簡單方法是嘗試上述任何客戶端,或者僅嘗試redis-cli命令。如下是使用後者進行交互的示例:

$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

注意:若是使用腳本建立集羣,則節點可能會偵聽不一樣的端口,默認狀況下從30001開始。

redis-cli的支持很是基礎,所以它始終基於如下事實:Redis集羣節點可以將客戶端重定向到正確的節點。一個嚴格的客戶端能夠作得更好,而且能夠在hash槽和節點地址之間緩存映射,以便直接使用與節點的正確鏈接。僅在集羣配置中發生某些更改時(例如,在故障轉移以後或系統管理員經過添加或刪除節點來更改集羣佈局以後),纔會刷新映射。

使用redis-rb-cluster寫一個簡單的應用程序

在繼續展現如何操做Redis集羣以前,好比執行故障轉移或從新分片之類的操做,咱們須要建立一些示例應用程序,或者至少要可以理解簡單的Redis集羣客戶端交互的語義。

經過這種方式,咱們能夠運行一個示例,同時嘗試使節點發生故障或開始從新分片,以瞭解Redis集羣在現實環境下的行爲。只是觀察一個沒有寫入任何數據的集羣是沒有幫助的。

本節說明了redis-rb-cluster的一些基本用法,其中顯示了兩個示例。 首先是如下內容,它是redis-rb-cluster發行版中的example.rb文件:

1  require './cluster'
   2
   3  if ARGV.length != 2
   4      startup_nodes = [
   5          {:host => "127.0.0.1", :port => 7000},
   6          {:host => "127.0.0.1", :port => 7001}
   7      ]
   8  else
   9      startup_nodes = [
  10          {:host => ARGV[0], :port => ARGV[1].to_i}
  11      ]
  12  end
  13
  14  rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1)
  15
  16  last = false
  17
  18  while not last
  19      begin
  20          last = rc.get("__last__")
  21          last = 0 if !last
  22      rescue => e
  23          puts "error #{e.to_s}"
  24          sleep 1
  25      end
  26  end
  27
  28  ((last.to_i+1)..1000000000).each{|x|
  29      begin
  30          rc.set("foo#{x}",x)
  31          puts rc.get("foo#{x}")
  32          rc.set("__last__",x)
  33      rescue => e
  34          puts "error #{e.to_s}"
  35      end
  36      sleep 0.1
  37  }

該程序作了一件很是簡單的事情,它將foo 形式的key的值設置爲數字,而且是一個接一個的遞增。
所以,若是您運行該程序,其結果將和如下命令是同樣的效果:

  • SET foo0 0
  • SET foo1 1
  • SET foo2 2
  • ...

該程序看起來比較複雜,由於它須要在屏幕上顯示錯誤而不是異常退出,所以,對集羣執行的每一個操做都應該由錯誤處理包裝。

第14行是程序中的第一個有趣的行。它建立Redis集羣對象,使用啓動節點列表做爲參數,並容許該對象與不一樣節點創建的最大鏈接數,最後是超時時間,對於給定的操做多少時間後被視爲失敗。

啓動節點不須要是集羣的全部節點。但至少有一個節點是可達的。還要注意,只要可以與第一個節點鏈接,redis-rb-cluster就會更新此啓動節點列表。您應該指望任何其餘嚴格的客戶端都應該採起這種行爲。

如今咱們已經將Redis集羣對象實例存儲在rc變量中,咱們能夠像使用普通的Redis對象實例同樣使用該對象了。

這剛好發生在第18至26行中:從新啓動示例時,咱們不想以foo0從新開始,所以咱們將計數器存儲在Redis自己內。上面的代碼旨在讀取此計數器,或者若是不存在該計數器,則爲其分配零值。

可是請注意這是一個while循環,由於即便集羣關閉並返回錯誤,咱們也要一次又一次嘗試。普通的應用程序不須要那麼當心。

28和37之間開始主循環,在該循環中設置key或顯示錯誤。

注意循環結束時的sleep調用。在測試中,若是您想盡量快地寫入集羣,則能夠刪除sleep(相對來講,這只是一個很繁忙的循環操做,它並無真正的並行,所以,在最好的條件下,您一般將得到每秒10k個操做))。

一般,爲了使示例程序更容易被人看懂,寫入速度會減慢。啓動應用程序將產生如下輸出:

ruby ./example.rb
1
2
3
4
5
6
7
8
9
^C (I stopped the program here)

這不是一個很是有趣的程序,咱們稍後將使用更好的程序,可是咱們已經能夠看到程序運行時,在從新分片期間都發生了什麼。

集羣從新分片

如今,咱們準備嘗試集羣從新分片。 爲此,請保持example.rb程序運行,以便您查看對程序的運行是否有影響。另外,您可能想註釋一下sleep調用,以便在從新分片期間發生一些更嚴重的寫入負載。從新分片基本上意味着將hash槽從一組節點移動到另外一組節點,而且像集羣建立同樣,它使用redis-cli程序完成。

要開始從新分片,只需鍵入:

redis-cli --cluster reshard 127.0.0.1:7000

您只須要指定一個節點,redis-cli將自動找到其餘節點。

當前redis-cli僅能在管理員支持下從新分片,您不能僅僅說將5%的插槽從該節點移到另外一個節點(固然這實現起來很簡單)。 所以,它會以一個問題開始。 首先是您想作多少重分片:

How many slots do you want to move (from 1 to 16384)?

咱們能夠嘗試從新分派1000個hash槽,若是該示例仍在運行且沒有sleep調用,則該hash槽應已包含少許的key。

而後redis-cli須要知道從新分片的目標是什麼,也就是將接收hash槽的節點。 我將使用第一個主節點,即127.0.0.1:7000,可是我須要指定實例的節點ID。redis-cli已將其打印在列表中,可是若是須要的話,我也可使用如下命令找到節點的ID:

$ redis-cli -p 7000 cluster nodes | grep myself
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460

因此個人目標節點應該是是 97a3a64667477371c4479320d683e4c8db5858b1。

如今,它會問你要從哪些節點獲取這些key。我只輸入all,以便從全部其餘主節點獲取一些hash槽。

最終確認後,您會看到一條消息,代表redis-cli將要從一個節點移動到另外一個節點,而且將從一側移動到另外一側的每一個實際的key都會打印出來。

在從新分片過程當中,您應該可以看到示例程序運行不受影響。若是須要,您還能夠在從新分片期間中止並從新啓動它屢次。從新分片結束時,可使用如下命令測試集羣的運行情況:

redis-cli --cluster check 127.0.0.1:7000

全部插槽都會被覆蓋到,可是此次127.0.0.1:7000的主節點將具備更多的hash插槽
,大約爲6461。

一個更有趣的示例應用程序

咱們以前編寫的示例程序不怎麼好。它以一種簡單的方式寫入集羣,甚至無需檢查寫入的內容是否正確。從咱們的角度來看,接收寫操做的集羣能夠始終在每一個操做裏將名爲foo的key寫到42這個hash槽裏,而咱們根本不會注意到。所以,在redis-rb-cluster代碼倉庫中,有一個更有趣的程序,稱爲consistency-test.rb。它使用一組計數器,默認爲1000,而且發送INCR命令以增長計數器的值。可是,該應用程序不只能夠寫數據,還能夠作兩件事:

  • 當使用INCR更新計數器時,應用程序會記住該寫入。
  • 它還在每次寫入以前讀取一個隨機計數器,並檢查該值是否符合咱們的預期,並將其與內存中的值進行比較。

這意味着該程序是一個簡單的一致性檢查程序,能夠告訴您集羣是否丟失了一些寫操做,或者它是否接受了咱們未收到確認的寫操做。在第一種狀況下,咱們將看到一個計數器的值小於咱們以前記住的值,而在第二種狀況下,該值將更大。

運行一致性測試應用程序每秒產生一行輸出:

$ ruby consistency-test.rb
925 R (0 err) | 925 W (0 err) |
5030 R (0 err) | 5030 W (0 err) |
9261 R (0 err) | 9261 W (0 err) |
13517 R (0 err) | 13517 W (0 err) |
17780 R (0 err) | 17780 W (0 err) |
22025 R (0 err) | 22025 W (0 err) |
25818 R (0 err) | 25818 W (0 err) |

該行顯示執行的讀取和寫入的次數,以及錯誤的數目(因爲系統不可用,所以因爲錯誤而沒法接受查詢)。若是發現不一致,則將新行添加到輸出中。例如,若是我在程序運行時手動重置了計數器,就會發生這種狀況:

$ redis-cli -h 127.0.0.1 -p 7000 set key_217 0
OK

(in the other tab I see...)

94774 R (0 err) | 94774 W (0 err) |
98821 R (0 err) | 98821 W (0 err) |
102886 R (0 err) | 102886 W (0 err) | 114 lost |
107046 R (0 err) | 107046 W (0 err) | 114 lost |

當我將計數器設置爲0時,實際值爲114,所以程序會報告114的寫丟失了(集羣沒法記住的INCR命令)。該程序做爲測試用例更加有趣,所以咱們將使用它來測試Redis 集羣故障轉移。

測試故障轉移

注意:在此測試過程當中,你應打開一個tab標籤頁並在上面運行一致性測試應用程序。

爲了觸發故障轉移,咱們能夠作的最簡單的事情(也就是在分佈式系統中可能發生的語義上最簡單的失敗)是使單個進程崩潰,在咱們的例子中是單個主機崩潰。

咱們可使用如下命令來識別主節點並使其崩潰:

$ redis-cli -p 7000 cluster nodes | grep master
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385482984082 0 connected 5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 master - 0 1385482983582 0 connected 11423-16383
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422

好了,如今7000,7001,7002都是主節點,咱們把7002這臺機器用DEBUG SEGFAULT命令使其崩潰。

$ redis-cli -p 7002 debug segfault
Error: Server closed the connection

如今咱們能夠看看這個一致性測試的輸出的報告是什麼。

18849 R (0 err) | 18849 W (0 err) |
23151 R (0 err) | 23151 W (0 err) |
27302 R (0 err) | 27302 W (0 err) |

... many error warnings here ...

29659 R (578 err) | 29660 W (577 err) |
33749 R (578 err) | 33750 W (577 err) |
37918 R (578 err) | 37919 W (577 err) |
42077 R (578 err) | 42078 W (577 err) |

如您所見,在故障轉移期間,系統沒法接受578次讀取和577次寫入,可是在數據庫中並未建立任何不一致的數據。

這聽起來多是個意外,由於在本教程的第一部分中,咱們說過Redis集羣在故障轉移期間會丟失寫操做,由於它使用異步複製。咱們沒有說的是,這其實不太可能發生,由於Redis會給客戶端發送迴應,而且一樣的命令幾乎同時會複製到備份節點,所以丟失數據的窗口很小。可是,很難觸發這一事實並不意味着它不可能,所以這不會改變Redis集羣提供的一致性保證。

如今,咱們能夠檢查故障轉移以後的集羣設置是什麼(請注意,我從新啓動了崩潰的實例,以便它做爲備份節點從新加入集羣):

$ redis-cli -p 7000 cluster nodes
3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385503418521 0 connected
a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385503419023 0 connected
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5959 10922-11422
3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385503419023 3 connected 11423-16383
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385503417005 0 connected 5960-10921
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385503418016 3 connected

如今,主節點在端口7000、7001和7005上運行。之前是主節點(在端口7002上運行的Redis實例)如今變成了7005的備份節點。

CLUSTER NODES命令的輸出可能看起來很複雜,但實際上很是簡單,由如下標記組成:

  • 節點ID
  • ip:端口
  • 標誌位: 主節點,備份節點, myself, 失敗狀態, ...
  • 若是本身是備份節點,則是其主節點的ID
  • 上一次發出PING後還未收到回覆的持續時間.
  • 上一次接收到的PONG的時間.
  • 節點的配置epoch (請看 集羣規範).
  • 此節點的連接狀態.
  • 服務的插槽...

手動故障轉移

有時,強制進行故障轉移而實際上不會對主節點引發任何問題是頗有用的。例如,爲了升級主節點之一的Redis進程,最好對其進行故障轉移,以將其轉變爲對可用性的影響最小的備份節點。

Redis集羣使用CLUSTER FAILOVER 命令支持手動故障轉移,該手動故障轉移必須在要進行故障轉移的主節點的備份節點之一中執行。

與實際的主服務器故障致使的故障轉移相比,手動故障轉移是不同的,但它更安全,由於它們觸發的方式避免了此過程當中的數據丟失,只有在系統肯定新的主節點已經在運行而且替代了舊的主節點的數據複製功能後,才能將客戶端從原來的主節點切換到新的主節點。

在執行手動故障轉移時在備份節點日誌中能夠看到:

# Manual failover user request accepted.
# Received replication offset for paused master manual failover: 347540
# All master replication stream processed, manual failover can start.
# Start of election delayed for 0 milliseconds (rank #0, offset 347540).
# Starting a failover election for epoch 7545.
# Failover election won: I'm the new master.

基本上,鏈接到咱們將要進行故障轉移的主節點的客戶端都已中止。同時,主節點將其複製偏移發送到備份節點,備份節點會在它這邊等待偏移接收完畢。 當複製偏移量完成時,故障轉移開始,而且將向舊的主節點通知配置切換。 當客戶端在舊的主節點上解鎖時,它們將被重定向到新的主節點。

添加新節點

添加新節點的基本過程是先添加一個空節點,而後將一些數據移入該節點(若是它是新的主節點),或者告訴它設置爲已知節點的副本(若是它是備份節點)。從添加新的主節點開始,咱們二者都會展現。在這兩種狀況下,要執行的第一步都是添加一個空節點。這就像在端口7006中啓動一個新節點(現有的6個節點已經從7000到7005使用新節點)同樣簡單,除了端口號以外,其餘節點都使用相同的配置,所以您應該按順序進行操做以符合咱們以前節點使用的設置:

  • 在你的終端應用上開啓一個新的tab。
  • 輸入 cluster-test 目錄.
  • 建立一個名字爲7006的文件夾.
  • 在文件夾裏建立redis.conf文件, 就跟其餘已經在使用的節點同樣,只是換成了7006端口.
  • 最後,經過命令 ../redis-server ./redis.conf 啓動服務,

此時這個服務應該運行起來了。如今咱們可使用redis-cli來向已有的集羣添加一個節點。

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000

如您所見,我使用add-node命令將新節點的地址指定爲第一個參數,並將集羣中隨機存在的節點的地址指定爲第二個參數。實際上,redis-cli在這裏對咱們沒什麼用,它只是向節點發送了CLUSTERMEET消息,這也能夠手動完成。不過redis-cli會在運行以前檢查集羣的狀態,所以,即便您知道內部結構如何運行,經過redis-cli執行集羣操做是仍然是一個好主意。

如今,咱們能夠鏈接到新節點,以查看它是否確實加入了集羣:

redis 127.0.0.1:7006> cluster nodes
3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 127.0.0.1:7001 master - 0 1385543178575 0 connected 5960-10921
3fc783611028b1707fd65345e763befb36454d73 127.0.0.1:7004 slave 3e3a6cb0d9a9a87168e266b0a0b24026c0aae3f0 0 1385543179583 0 connected
f093c80dde814da99c5cf72a7dd01590792b783b :0 myself,master - 0 0 0 connected
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543178072 3 connected
a211e242fc6b22a9427fed61285e85892fa04e08 127.0.0.1:7003 slave 97a3a64667477371c4479320d683e4c8db5858b1 0 1385543178575 0 connected
97a3a64667477371c4479320d683e4c8db5858b1 127.0.0.1:7000 master - 0 1385543179080 0 connected 0-5959 10922-11422
3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7005 master - 0 1385543177568 3 connected 11423-16383

請注意,因爲此節點已經鏈接到集羣,所以它已經可以正確重定向客戶端查詢,一般來講它已是集羣的一部分了。 可是,與其餘主節點相比,它有兩個特色:

  • 因爲沒有分配的hash槽,所以不保存任何數據。
  • 由於它是沒有分配插槽的主機,因此當備份節點要成爲主節點時,它不會參與選
    舉過程。

如今可使用redis-cli的從新分片功能將hash槽分配給該節點。像上一節中已經展現的那樣,這裏我就不展現了,他們的操做沒有區別,只是將空節點做爲目標進行從新分片。

添加一個節點做爲副本(備份節點)

添加一個備份節點能夠經過2種方式完成。最經常使用的是用 redis-cli, 不過要用--cluster-slave選項,就像這樣:

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave

請注意,此處的命令行與咱們用於添加新主節點的命令行徹底相同,所以咱們並未指定要向其添加副本的主節點。在這種狀況下,redis-cli要作的就是將新節點添加給副本較少的主節點中的隨機主節點的副本。可是,您可使用如下命令行指定想要與新副本一塊兒使用的主節點:

redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave --cluster-master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

這樣咱們便將新副本分配給特定的主節點。

將副本添加到特定主節點的一種更手動的方法是將新節點添加爲空的主節點,而後使用CLUSTER REPLICATE命令將其轉換爲副本。 若是將該節點添加爲備份節點,但您想將其做爲其餘主節點的副本進行移動,也同樣適用。

例如,爲了給節點127.0.0.1:7005添加副本,此節點當前服務的hash槽正在11423-16383範圍內,節點ID爲3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我要作的就是鏈接到新節點(已經做爲空的主節點添加到集羣)並在新節點上發送命令:

redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e

就是這樣。 如今,這組hash槽有了一個新副本,而且集羣中的全部其餘節點都已經知道(幾秒鐘後須要更新其配置)。 咱們可使用如下命令進行驗證:

$ redis-cli -p 7000 cluster nodes | grep slave | grep 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
f093c80dde814da99c5cf72a7dd01590792b783b 127.0.0.1:7006 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617702 3 connected
2938205e12de373867bf38f1ca29d31d0ddb3e46 127.0.0.1:7002 slave 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 0 1385543617198 3 connected

節點3c3a0c...如今有了2個備份節點,運行在7002端口(已存在)和7006端口(新加入的)

移除節點

爲了移除一個備份節點,只須要在redis-cli上使用del-node 命令:

redis-cli --cluster del-node 127.0.0.1:7000 `<node-id>`

第一個參數只是集羣中的一個隨機節點,第二個參數是您要刪除的節點的ID。您也能夠用相同的方法刪除主節點,可是要刪除主節點,它必須爲空。若是主節點不爲空,則須要先將數據從新分片到全部其餘主節點。

刪除主節點的另外一種方法是在其一個備份節點上對其執行手動故障轉移,並在該節點成爲新主節點的備份節點以後刪除該節點。顯然,這在您想要減小集羣中的主節點的實際數量時沒什麼用,在這種狀況下,須要從新分片。

副本遷移

在Redis集羣裏裏任什麼時候間你均可以從新配置一個備份節點使其做爲另外一個主節點的從屬節點,使用下列命令:

CLUSTER REPLICATE <master-node-id>

可是,有一種特殊狀況,您但願副本在沒有系統管理員幫助的狀況下自動從一個主節點移動到另外一個主節點。副本的自動從新配置稱爲副本遷移,它可以提升Redis集羣的可靠性。

注意:您能夠在Redis集羣規範中閱讀副本遷移的詳細信息,這裏咱們僅提供一些通常的想法以及您應該從中受益的信息。

在某些狀況下,您可能想讓您的集羣副本從一個主節點移動到另外一個主節點的緣由是,Redis集羣一般具備與給定主節點的副本數量相同的故障容忍性。

例如,若是一個主節點及其副本同時失敗,則每一個主節點都有一個副本的集羣將沒法繼續工做,這僅僅是由於沒有其餘實例擁有與該主節點服務的相同的hash槽的副本。可是,儘管網絡斷裂可能會同時隔離多個節點,可是許多其餘類型的故障(例如單個節點本地的硬件或軟件故障)是很是值得注意的一類故障,這類故障不太可能同時發生,所以在每一個主節點都有一個備份節點的集羣中,若是該備份節點在凌晨4點被關閉,而主節點在凌晨6點被關閉。這仍然會致使集羣沒法運行。

爲了提升系統的可靠性,咱們能夠選擇向每一個主節點添加副本,但這成本很高。副本遷移容許將更多備份節點添加到少數幾個主節點中。所以,您有10個節點,每一個節點有1個備份節點,總共20個實例。可是,好比您增長了3個實例做爲某些主節點的備份節點,所以某些主節點將具備多個副本了。

使用副本遷移時,若是一個主節點不包含備份節點,則具備多個備份節點的主節點的副本將遷移到孤立的主節點。所以,當您的備份節點在上述示例中的凌晨4點關閉以後,另外一個備份節點將接替它;當主節點在凌晨5點也發生故障時,另外一個備份節點將被選舉成爲主節點,以便集羣能夠繼續操做。

因此您應該瞭解哪些有關副本遷移的知識?

  • 在某個時刻,集羣會嘗試從具備最多副本數的主節點中選擇一個副本進行遷移。
  • 爲了從副本遷移中受益,您只需爲集羣中的單個主節點添加一些副本,無論是哪一個主節點
  • 有一個配置參數可控制副本遷移功能,稱爲cluster-migration-barrier:您可
    以在Redis集羣隨附的示例redis.conf文件中瞭解有關此功能的更多信息。
    參考
相關文章
相關標籤/搜索