Redis學習之Redis Cluster(三)

Redis Cluster

Redis Cluster

Redis Cluster提供了一種運行Redis安裝的方法,其中數據 在多個Redis節點之間自動分片java

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

因此實際上,你對Redis Cluster有什麼見解?git

  • 可以在多個節點之間自動拆分數據集
  • 當節點的子集遇到故障或沒法與集羣的其他部分通訊時,可以繼續操做

Redis集羣TCP端口

每一個Redis集羣節點都須要打開兩個TCP鏈接。用於爲客戶端提供服務的普通Redis TCP端口,例如6379,加上經過向數據端口添加10000得到的端口,所以示例中爲16379。github

第二個端口用於集羣總線,即便用二進制協議的節點到節點的通訊通道。節點使用集羣總線進行故障檢測,配置更新,故障轉移受權等。客戶端永遠不該嘗試與集羣總線端口通訊,但始終使用正常的Redis命令端口,但請確保在防火牆中打開兩個端口,不然Redis集羣節點將沒法通訊。redis

命令端口和集羣總線端口偏移是固定的,始終爲10000。docker

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

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

若是不打開兩個TCP端口,則集羣將沒法按預期工做。promise

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

Redis集羣和Docker

目前,Redis集羣不支持NATted環境,也不支持從新映射IP地址或TCP端口的通常環境。安全

Docker使用一種稱爲端口映射的技術:與程序使用的端口相比,在Docker容器內運行的程序可能會使用不一樣的端口。這對於在同一服務器中同時使用相同端口運行多個容器很是有用。

爲了使Docker與Redis Cluster兼容,您須要使用Docker 的主機網絡模式。有關更多信息,請查看Docker文檔中--net=host選項。

Redis集羣數據分片

Redis Cluster不使用一致的散列,而是使用不一樣形式的分片,其中每一個鍵在概念上都是咱們稱之爲散列槽的一部分

Redis集羣中有16384個散列槽,爲了計算給定key的散列槽,咱們只需採用key模數爲16384的CRC16。

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

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

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

由於將哈希槽從一個節點移動到另外一個節點不須要中止操做,添加和刪除節點,或者更改節點所持有的哈希槽的百分比,因此不須要任何停機時間。

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

Hash標籤記錄在Redis集羣規範中,但要點是若是key中{}括號之間有子字符串,則只對字符串內部的內容進行哈希處理,例如,保證this{foo}keyanother{foo}key 位於相同的哈希槽中,可使多個鍵在一個參數的命令中一塊兒使用。

Redis Cluster主從模型

爲了在主節點子集發生故障或沒法與大多數節點通訊時保持可用,Redis Cluster使用主從模型,其中每一個散列槽從1(主機自己)到N個副本(N-1個額外的從節點)。

在具備節點A,B,C的示例集羣中,若是節點B發生故障,則集羣沒法繼續,由於咱們再也不可以在5501-11000範圍內提供服務哈希位置的方法。

然而,當建立集羣時(或稍後),咱們向每一個主節點添加一個從節點,以便最終集羣由做爲主節點的A,B,C和做爲從節點的A1,B1,C1組成。 ,若是節點B出現故障,系統就能繼續運行。

節點B1複製B,B失敗,集羣將節點B1升級爲新的主節點,並將繼續正常運行。

但請注意,若是節點B和B1同時發生故障,Redis Cluster將沒法繼續運行。

Redis集羣一致性保證

Redis Cluster沒法保證強一致性。實際上,這意味着在某些條件下,Redis Cluster可能會丟失系統向客戶端確認的寫入。

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

  • 您的客戶端寫入主B。
  • masterB向您的客戶回覆肯定。
  • 主設備B將寫入傳播到其從設備B1,B2和B3。

正如你所看到的,B在回覆客戶端以前並無等待來自B1,B2,B3的確認,由於這對Redis來講是一個太高的延遲懲罰,因此若是你的客戶端寫了一些內容,B會確認寫入,可是崩潰在寫入發送到其從屬以前,其中一個從屬(沒有接收到寫入)能夠提高爲master,將會永遠丟失寫入。

這與配置爲每秒將數據刷新到磁盤的大多數數據庫所發生的狀況很是類似,所以,因爲過去使用不涉及分佈式系統的傳統數據庫系統的經驗,所以您已經可以推斷這種狀況。一樣,您能夠經過在回覆客戶端以前強制數據庫刷新磁盤上的數據來提升一致性,但這一般會致使性能太低。在Redis Cluster的狀況下,這至關於同步複製。

基本上須要在性能和一致性之間進行權衡。

Redis Cluster在絕對須要時支持同步寫入,經過WAIT命令實現,這使得丟失寫入的可能性大大下降,但請注意,即便使用同步複製,Redis Cluster也不會實現強一致性:在更復雜的狀況下老是能夠實現失敗場景,沒法接收寫入的slave被選爲master。

還有另外一個值得注意的狀況是,Redis集羣將丟失寫入,這種狀況發生在網絡分區中,其中客戶端與少數實例(至少包括主服務器)隔離。

以6個節點簇爲例,包括A,B,C,A1,B1,C1,3個master和3個slave。還有一個客戶,咱們稱之爲Z1。

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

Z1仍然能夠寫入B,它將接受其寫入。若是分區在很短的時間內恢復,集羣將繼續正常運行。可是,若是分區持續足夠的時間使B1在分區的多數側被提高爲主,則Z1發送給B的寫入將丟失。

請注意,Z1將可以發送到B的寫入量存在最大窗口:若是分區的多數方面已經有足夠的時間將從屬設備選爲主設備,則少數端的每一個主節點都會中止接受寫入。

這段時間是Redis Cluster的一個很是重要的配置指令,稱爲節點超時

節點超時事後,主節點被視爲失敗,能夠由其中一個副本替換。相似地,在節點超時已通過去而主節點沒法感知大多數其餘主節點以後,它進入錯誤狀態並中止接受寫入。

Redis集羣配置參數

咱們即將建立一個示例集羣部署。在繼續以前,讓咱們介紹Redis Cluster在redis.conf文件中引入的配置參數。有些會很明顯,有些會在你繼續閱讀時更清楚。

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

建立和使用Redis集羣

注意:要手動部署Redis集羣**,瞭解它的某些操做方面很是重要**。可是,若是要啓動集羣並儘快運行,請跳過本節和下一節,而後直接使用create-cluster腳本建立Redis集羣

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

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

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
複製代碼

正如您所看到的,啓用集羣模式的只是cluster-enabled 指令。每一個實例還包含存儲此節點配置的文件路徑,默認狀況下爲nodes.conf。這個文件永遠不會被人類接觸;它只是在Redis Cluster實例啓動時生成,並在每次須要時更新。copy的時候,記得修改nodes.conf的名字,如nodes-7001.conf

請注意,按預期工做的最小集羣須要包含至少三個主節點。對於您的第一次測試,強烈建議啓動具備三個主設備和三個從設備的六節點集羣。

爲此,請輸入一個新目錄,並建立以咱們將在任何給定目錄中運行的實例的端口號命名的如下目錄。

就像是:

mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
複製代碼

redis.conf在每一個目錄中建立一個文件,從7000到7005做爲配置文件的模板,只需使用上面的小例子,但請確保根據目錄名稱用正確的端口號替換端口號7000

如今將從GitHub的unstable分支中的最新源編譯的 redis-server可執行文件複製到cluster-test目錄中,最後在您喜歡的終端應用程序中打開6個終端tabs。

像這樣開始每一個實例,每一個tab一個:

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地址和端口可能會發生變化,但惟一的節點標識符永遠不會在節點的整個生命週期內發生變化。咱們稱這個標識符爲Node ID

建立集羣

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

若是您使用的是Redis 5,這很容易實現,由於咱們在嵌入的Redis Cluster命令行實用程序redis-cli的幫助下,可用於建立新集羣,檢查或從新塑形現有集羣等等。

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

gem install redis
複製代碼

第一個例子,即集羣建立,將在Redis 5的redis-cli和Redis 3和4中的redis-trib同時顯示。可是全部下面的例子都只會使用redis-cli,由於你能夠看到語法很是類似,你能夠經過使用redis-trib.rb help獲取有關舊語法的信息將一個命令行更改成另外一個命令行。**重要提示:**請注意,若是您願意,可使用Redis 5 的redis-cli代替Redis 4集羣。

要爲Redis 5建立集羣,redis-cli只需鍵入(若是redis有密碼記得加入-a參數,要遠程調用請配置公網ip):

redis-cli -a 123456 --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-trib.rb用於Redis的4或3:

./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節點集羣,只需鍵入如下命令:

一、create-cluster start

二、create-cluster create

yesredis-cli實用程序但願您接受集羣佈局時,在步驟2中回覆。

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

一、create-cluster stop

README有關如何運行腳本的更多信息,請閱讀此目錄中的內容。

玩集羣

在此階段,Redis Cluster的一個問題是缺乏客戶端庫實現。

我知道如下實現:

  • redis-rb-cluster是由我(@antirez)編寫的做爲其餘語言的參考的Ruby實現。它是原始redis-rb的簡單包裝器,實現了最小的語義,能夠有效地與集羣通訊。
  • redis-py-cluster redis-rb-cluster到Python的一個端口。支持大多數redis-py功能。正在積極發展。
  • 流行的Predis支持Redis Cluster,最近更新了支持而且正在積極開發中。
  • 最經常使用的Java客戶端Jedis最近添加了對Redis Cluster的支持,請參閱項目README中的Jedis Cluster部分。
  • StackExchange.Redis提供對C#的支持(而且應該適用於大多數.NET語言; VB,F#等)
  • thunk-redis支持Node.js和io.js,它是一個基於thunk / promise的redis客戶端,具備流水線和集羣。
  • redis-go-cluster是使用Redigo庫客戶端做爲基本客戶端的Go語言的Redis集羣的實現。經過結果聚合實現MGET / MSET。
  • 在GitHub上的Redis的存儲庫中的不穩定分支中的redis-cli工具實現了一個很是基本的集羣支持-c開關。

測試Redis Cluster的一種簡單方法是嘗試上述任何客戶端或僅使用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集羣節點可以將客戶端重定向到右節點的事實。一個認真的客戶端可以作得更好,並在哈希槽和節點地址之間緩存地圖,以直接使用與正確節點的正確鏈接。僅當集羣配置中的某些內容發生更改時(例如,在故障轉移以後或系統管理員經過添加或刪除節點更改集羣佈局後),纔會刷新映射。

使用redis-rb-cluster編寫示例應用程序

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

經過這種方式,咱們能夠運行一個示例,同時嘗試使節點失敗,或者開始從新分片,以瞭解Redis Cluster在真實條件下的行爲方式。觀察集羣時沒有數據寫入並非頗有幫助。

本節介紹了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<number>一個接一個設置爲number。所以,若是您運行該程序,結果是如下命令流:

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

該程序看起來比它應該更復雜,由於它被設計爲在屏幕上顯示錯誤而不是以異常退出,所以使用集羣執行的每一個操做都由begin rescue塊包裝。

14行是該程序中第一個有趣的行。它建立Redis Cluster對象,使用startup nodes list做爲參數,容許此對象對不一樣節點採用不一樣的最大鏈接數,最後在給定timeout,被認爲給定的失敗操做。

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

既然咱們已將Redis Cluster對象實例存儲在rc變量中,咱們就可使用該對象,就好像它是一個普通的Redis對象實例同樣。

這正是第18到26行所發生的事情:當咱們從新啓動示例時,咱們不想再次啓動foo0,所以咱們將計數器存儲在Redis自己中。上面的代碼用於讀取此計數器,或者若是計數器不存在,則將其賦值爲零。

但請注意它是如何循環,由於咱們想要反覆嘗試,即便集羣已關閉並返回錯誤。普通應用程序不須要那麼當心。

28到37之間的行啓動主循環,其中設置了鍵或顯示錯誤。

注意sleep循環結束時的調用。在你的測試中,若是你想盡量快地寫入集羣,你能夠刪除sleep(相對於這是一個繁忙的循環,固然沒有真正的並行性,因此在最好的條件下,你一般會獲得10k的操做/秒)。

一般,寫入速度會下降,以便示例應用程序更容易被人類遵循。

啓動應用程序會生成如下輸出:

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

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

從新整理集羣

如今咱們準備嘗試集羣從新分片了。爲此,請保持example.rb程序運行,以便您能夠查看是否對運行的程序有一些影響。此外,您可能想要繼續執行該sleep 調用,以防止在從新分片期間有一些更嚴重的寫入負載。

從新分片基本上意味着將散列槽從一組節點移動到另外一組節點,而且像集羣建立同樣,它是使用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個散列槽,若是示例在沒有調用sleep的狀況下運行,那麼該散列槽應該包含很是少許的key。

而後redis-cli須要知道從新分片的目標是什麼,即接收哈希槽的節點。我將使用第一個主節點,即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以便從全部其餘主節點獲取一些哈希槽。

在最終確認以後,您將看到redis-cli將從一個節點移動到另外一個節點的每一個插槽的消息,而且將爲從一側移動到另外一側的每一個實際鍵打印一個點。

從新分片正在進行中時,您應該可以看到您的示例程序不受影響地運行。若是須要,您能夠在從新分片期間屢次中止並從新啓動它。

在從新分片結束時,您可使用如下命令測試集羣的運行情況:

redis-cli --cluster check 127.0.0.1:7000

全部的插槽都會像往常同樣被覆蓋,但此次127.0.0.1:7000的主機將有更多的散列槽,大約6461。

編寫從新分析操做的腳本

能夠自動執行從新分片,而無需以交互方式手動輸入參數。這可使用以下命令行:

redis-cli reshard <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes
複製代碼

若是您可能常常從新設置,則容許構建一些自動操做,可是目前沒法redis-cli自動從新平衡集羣,檢查跨集羣節點的key分發以及根據須要智能地移動插槽。此功能將在將來添加。

手動故障轉移

有時強制進行故障轉移而不會在主服務器上形成任何問題。例如,爲了升級其中一個主節點的Redis進程,最好對其進行故障轉移,以便將其轉換爲從屬,對可用性的影響最小。

Redis Cluster使用CLUSTER FAILOVER 命令支持手動故障轉移,該命令必須在要故障轉移的master的一個slave中執行。

手動故障轉移是特殊的,與實際主故障致使的故障轉移相比更安全,由於它們以免數據丟失的方式發生,經過僅在系統肯定新的主服務器時將客戶端從原始主服務器切換到新主服務器master處理了舊的複製流。

這是您在執行手動故障轉移時在從屬日誌中看到的內容:

# 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中啓動一個新節點(咱們已經在7000到7005之間使用現有的6個節點),其餘節點使用相同的配置,端口號除外,因此你應該按順序作什麼符合咱們用於之前節點的設置:

  • 在終端應用程序中建立一個新選項卡。
  • 輸入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在這方面作的不多幫助咱們,它只是向節點發送了一個CLUSTER MEET消息,這也能夠手動完成。可是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
複製代碼

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

  • 它沒有數據,由於它沒有分配的哈希槽。
  • 由於它是沒有分配插槽的主設備,因此當從設備想要成爲主設備時,它不參與選舉過程。

如今可使用resharding功能爲此節點分配哈希槽redis-cli。就像咱們在上一節中所作的那樣,它只是將一個空節點從新分區的過程。

將新節點添加爲副本

添加新副本能夠經過兩種方式執行。顯而易見的是再次使用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命令將其轉換爲副本 。若是將節點添加爲從屬節點但您想將其做爲不一樣主節點的副本移動,則此方法也有效。

例如,爲了添加當前服務於11423-16383範圍內的哈希槽的節點127.0.0.1:7005的副本,其具備節點ID3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e,我須要作的就是鏈接新節點(已經添加爲空主)併發送命令:

redis 127.0.0.1:7006> cluster replicate 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e
複製代碼

而已。如今咱們爲這組哈希槽提供了一個新的副本,而且集羣中的全部其餘節點都已知道(須要幾秒鐘後才能更新它們的配置)。咱們可使用如下命令進行驗證:

$ 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 ...如今具備兩個從設備,在端口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集羣與附加到給定主服務器的副本數量同樣能夠抵禦故障。

例如,若是主服務器及其副本同時發生故障,則每一個主服務器具備單個副本的集羣沒法繼續操做,緣由很簡單,由於沒有其餘實例能夠擁有主服務器所服務的散列插槽的副本。然而,雖然netsplits可能同時隔離多個節點,但許多其餘類型的故障(如單個節點本地的硬件或軟件故障)是一類很是值得注意的故障,不太可能同時發生,因此有可能在你的集羣中每一個master都有一個slave,slave在凌晨4點被殺死,master在早上6點被殺死。這仍然會致使集羣沒法再運行。

爲了提升系統的可靠性,咱們能夠選擇爲每一個master添加額外的副本,但這很昂貴。副本遷移容許向少數主服務器添加更多從服務器。因此你有10個master,每一個master有1個slave,總共20個實例。可是,例如,您添加了3個實例做爲某些主服務器的從屬服務器,所以某些主服務器將擁有多個服務器。

對於副本遷移,發生的狀況是,若是master沒有slave,則來自具備多個slave的master的副本將遷移到孤立master。因此在咱們上面的例子中你的slave凌晨4點關閉以後,另外一個slave將佔據它的位置,當master在凌晨5點失敗時,仍然有一個slave能夠被選舉,以便集羣能夠繼續操做。

那麼您應該簡要了解副本遷移的內容?

  • 集羣將嘗試從給定時刻具備最大副本數的主服務器遷移副本。
  • 要從副本遷移中受益,您只須要向集羣中的單個主服務器添加一些副本,這與主服務器無關。
  • 有一個配置參數能夠控制所調用的副本遷移功能cluster-migration-barrier:您能夠在redis.confRedis Cluster提供的示例文件中閱讀有關它的更多信息。

Java客戶端

package com.lamarsan.cluster;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * className: Test
 * description: TODO
 *
 * @author hasee
 * @version 1.0
 * @date 2019/9/16 20:31
 */
public class Test {
    public static void main(String[] args) {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 最大鏈接數
        poolConfig.setMaxTotal(1);
        // 最大空閒數
        poolConfig.setMaxIdle(1);
        // 最大容許等待時間,若是超過這個時間還未獲取到鏈接,則會報JedisException異常:
        // Could not get a resource from the pool
        poolConfig.setMaxWaitMillis(1000);
        Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
        nodes.add(new HostAndPort("******", 7000));
        nodes.add(new HostAndPort("******", 7001));
        nodes.add(new HostAndPort("******", 7002));
        nodes.add(new HostAndPort("******", 7003));
        nodes.add(new HostAndPort("******", 7004));
        nodes.add(new HostAndPort("******", 7005));
        String password = "******";
        JedisCluster cluster = new JedisCluster(nodes, 10000, 1000, 1000, password, poolConfig);
        cluster.setnx("foo","bar");
        System.out.println(cluster.get("foo"));
        try {
            cluster.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

複製代碼
相關文章
相關標籤/搜索