Redis 集群教程
翻譯自官方文檔 Redis cluster tutorial
Redis 集群教程
該文檔是一篇關于redis集群的教程。該教程并不會讓你去理解復雜分布式系統概念,只會告訴你如何安裝、測試和操作一個集群。在這個過程中該文檔只會從用戶角度去描述系統的行為,并不會詳細的探究Redis集群手冊中的內容。
該教程會努力的從最終用戶的視角出發, 來介紹Redis集群的可用性和一致性這些特性。不過請放心,我們會用淺顯易懂的方式來介紹這一切。
注意:該教程需要Redis 的版本高于或者等于 3.0
雖然我們不強制要求你去閱讀手冊,但是如果你打算部署一個正式的Redis集群(比如生產環境用的redis集群),建議你閱讀更正式的手冊。 不過最好還是從這篇文檔開始玩redis,等你玩了一段時間redis之后再去閱讀手冊
Redis 集群101
Redis 集群提供了一個運行redis實例的方式,該方式下數據會被自動的在多個reids節點中分享。在分區的時候,Redis 集群還提供了一定程度的可用性,即在實際應用中,當幾個節點掛掉或者無法通訊的時候,系統還可以持續運行。不過當大面積的節點出問題的時候集群還是會停止(比如當主要的master掛掉了的時候)
那么在實際應用中,Redis集群可以做到什么?
- 自動切分數據集到多個節點上的能力
- 當部分節點宕機或無法通訊的情況下仍可繼續處理命令
Redis 集群 TCP 端口
每一個redis集群的節點需要開通兩個TCP端口。一個是用于客戶端的Redis TCP,如6379。另一個由客戶端加10000所得,如16379,用于Redis集群總線連接。 這是一個用戶 節點對節點的 二進制協議通訊通道。集群總線是用來處理節點的失效檢測,配置更新,災備授權等事情。客戶端應該連接redis普通命令端口(即之前提到的6370)而絕對不要去直接連接集群總線端口。不過還是要保證這兩個端口在防火墻里面打開,否則redis集群內的節點無法互相通信。
集群總線端口總是比命令端口高10000。
注意:為了讓redis集群運作正常,你需要在每個節點上:
- 把客戶端用來連接redis的普通客戶端通訊端口(一般是6379)對所有客戶端和其他節點開放(別的節點也會用這個端口來遷移數據)
- 所有節點之間的集群總線端口(客戶端口加上10000所得)必須互相開通
如果你沒有同時打開這兩個端口,集群就無法正常工作了
集群總線使用了一種不同的二進制協議,供節點和節點之間交換信息用。該協議可以讓節點和節點之間以更小的流量和和更短的時間來交換信息。
Redis 集群數據分片
Redis集群用的不是基于哈希值的分片方式,用的是另一種不同的分片方式。在該分片方式下所有鍵在概念上都是我們稱之為哈希槽的一部分。Redis集群有16384個哈希槽。當需要在 Redis 集群中放置一個 key-value 時,redis 先對 key 使用 crc16 算法算出一個結果,然后把結果對 16384 求余數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點。
Redis集群中的每個節點都存放了一些哈希槽。所以舉例來說,比如你有3個節點:
- 節點A 保存了從 0 到 5500 的哈希槽
- 節點B 保存了從 5501 到 11000的哈希槽
- 節點C 保存了從 11001 到 16384 的哈希槽
這么做讓集群增加或者減少節點變得很簡單。比如我要增加一個節點D,我只需要從節點ABC移動一部分哈希槽到D。如果我要從節點中去除節點A,我只需要把節點A上的哈希槽移動到節點B或者C。當節點A的哈希槽被全部移走了之后,我就可以將它從節點中完全去除。
因為把哈希槽從一個節點移動到另外一個節點并不需要停止集群, 所以增加、刪除節點或者在各節點間調整哈希槽的占有率的時候是不用停止集群的。Redis集群支持在一條命令里面對同一個哈希槽的多個鍵同時操作(或者在一個事務中,或者在一個lua腳本執行過程中)。用戶可以通過哈希標簽強制的把多個鍵放到一個哈希槽里面。在Redis集群手冊中可以查到哈希標簽的相關說明, 不過歸納成一句話就是:當有在key里面寫上段包含在{...}中的文字的時候,之后大括號{...}中的文字會被計算成哈希鍵。比如有兩個key一個名叫 this{foo}key 另一個名叫 another{foo}key ,這兩個key會被歸納到同一個哈希槽里面。這樣這兩個key就可以在一個命令中同時進行操作了。
Redis 集群主從模型
了在部分節點失敗或者無法通信的情況下集群仍然可用,Redis 集群采用了一種 主從模型。在該模型下每一個哈希槽都會被從master端復制N份到slave節點。在我們的例子中有三個節點分別是ABC,如果節點B掛掉了,集群就無法繼續工作,因為從5501到11000的哈希槽就沒了。
不過如果當集群被創建的時候(遲些時候也可以)我們給每一個master節點增加一個slave從節點。模型變成這樣:集群中有三個節點A,B,C,以及他們各自的從節點 A1,B1,C1,當節點B掛掉的時候,系統還可以正常運行。
節點B1是用來做為節點B的鏡像的。當節點B掛掉了,集群會選舉B1作為新的master節點,并繼續運行下去。
不過要注意當節點B和節點B1都掛掉的時候,redis集群還是無法繼續運行。
Redis 集群一致性保證
Redis 集群并不能保證數據的強一致性。在實際應用中這意味著在特定的情況下,就算Redis 集群告知客戶端已經收到了寫請求,這個寫請求仍然有可能丟失。Redis集群之所以會丟失寫請求的首要原因是:它采用了異步的復制機制。在寫的時候會經歷以下的步驟:
- 你的客戶端發送了一個寫請求給master B 節點
- master B 節點回復了一個OK給你的客戶端。
- master B 節點把這個寫請求傳播到它的 slave B1, B2, B3 節點上去。
正如你所見, B節點并不會等到B1,B2,B3節點都回復它之后才回復OK給客戶端。因為這樣會造成redis集群過高的延遲度。所以如果你的客戶端正在寫些什么東西,節點B又告知你的客戶端它收到了寫請求,但是在它把這個寫請求發送給它的slave節點們之前,節點B掛了,那么其中一個slave節點(假設它還沒收到寫請求)被選舉為master那么你這個寫請求就永久的丟失了。
很多數據庫被配置成每秒刷新一次數據到磁盤,他們都會發生非常類似的事情。所以你可以根據以往使用傳統數據庫(這些數據庫都不是基于分布式的)的經驗很容易的推導出這種場景。同樣的你也可以強制讓數據庫每次都等到寫入了磁盤才回復客戶端,這樣就可以保證一致性,但是這往往導致了系統的性能急劇下降。同樣的如果你把Reids集群設計成同步復制機制也會造成性能低下。
從根本上說這是一種用一致性來換取性能的交易。
當非常有必要的時候,Redis集群也支持同步寫入。它通過實現WAIT命令來實現。這樣一來基本不會丟失寫操作。但是請注意就算你使用了同步復制,Redis集群也不能達到強一致性,因為:總是會遇到某些更復雜的錯誤場景,在這些場景下slave節點在被選舉為master的時候還沒收到寫請求。
還有一個需要注意的會丟失數據的情況。當進行一次網絡網絡分裂的時候某個客戶端被分配到一個擁有很少節點的區域中的情況。
就拿我們的6節點例子(master是ABC,slave是A1,B1,C1),此時有一個客戶端,我們稱之為Z1。當網絡分裂后,有可能有這種情況:現在有2方,一方是 A,C,A1,B1,C1,另一方是B和Z1。Z1依然可以寫入B,而且B也會接受來自Z1的寫請求。如果這次網絡分裂在很短的時間內被修復, 集群依然會保持正常運行。 然而如果浙西網絡分裂持續了較長時間,長到足夠B1在多數方被選舉為master。那么Z1發送給B的寫請求都會丟失。
注意, 在網絡分裂出現期間, 客戶端 Z1 可以向主節點 B 發送寫命令的最大時間是有限制的, 這一時間限制稱為節點超時時間(node timeout), 是 Redis 集群的一個重要的配置選項:
- 對于大多數一方來說, 如果一個主節點未能在節點超時時間所設定的時限內重新聯系上集群, 那么集群會將這個主節點視為下線, 并使用從節點來代替這個主節點繼續工作。
- 對于少數一方, 如果一個主節點未能在節點超時時間所設定的時限內重新聯系上集群, 那么它將停止處理寫命令, 并向客戶端報告錯誤。
Redis 集群配置參數
我們來做一個redis集群的部署例子。在繼續后面的步驟之前我先介紹一下配置在redis.conf文件中的Redis集群參數。有些參數很容易懂,有些你必須接著讀以下的內容才會懂。
- cluster-enabled <yes/no>: 該項如果設置成yes,該實例支持redis集群。否則該實例會像往常一樣以獨立模式啟動。
- cluster-config-file <filename>: 必須注意到盡管該項是可選的,這并不是一個用戶可以編輯的配置文件,這是redis集群節點自動生成的配置文件,每次一旦配置有修改它都通過該配置文件來持久化配置(基本上都是狀態),這樣在下次啟動的時候可以重新讀取這些配置。該文件中列出了該集群中的其他節點的狀態,持久化變量等信息。 當節點收到一些信息的時候該文件就會被沖重寫。
- cluster-node-timeout <milliseconds>: redis集群節點的最大超時時間。響應超過這個時間的話該節點會被認為是掛掉了。如果一個master節點超過一定的時候無法訪問,它會被它的slave取代。 該參數在redis集群配置中很重要。很明顯,當節點無法訪問大部分master節點超過一定時間后,它會停止接受查詢請求。
- cluster-slave-validity-factor <factor>:如果將該項設置為0,不管slave節點和master節點間失聯多久都會一直嘗試failover(設為正數,失聯大于一定時間(factor*節點TimeOut),不再進行FailOver)。比如,如果節點的timeout設置為5秒,該項設置為10,如果master跟slave之間失聯超過50秒,slave不會去failover它的master(意思是不會去把master設置為掛起狀態,并取代它)。注意:任意非0數值都有可能導致當master掛掉又沒有slave去failover它,這樣redis集群不可用。在這種情況下只有原來那個master重新回到集群中才能讓集群恢復工作。
- cluster-migration-barrier <count>: 一個master可以擁有的最小slave數量。該項的作用是,當一個master沒有任何slave的時候,某些有富余slave的master節點,可以自動的分一個slave給它。具體參見手冊中的replica migration章節
- cluster-require-full-coverage <yes/no>: 如果該項設置為yes(默認就是yes) 當一定比例的鍵空間沒有被覆蓋到(就是某一部分的哈希槽沒了,有可能是暫時掛了)集群就停止處理任何查詢炒作。如果該項設置為no,那么就算請求中只有一部分的鍵可以被查到,一樣可以查詢(但是有可能會查不全)
創建并使用 Redis 集群
注意: 手動部署一個redis集群前學習這些操作很重要。但是如果你只是想最快速的搭建一個集群,你可以跳過這節和下一節直接看 用create-cluster腳本搭建redis集群。創建集群之前首要的一件事情是我們需要有一些運行在集群模式下的空節點。集群是不能在普通redis實例上創建的。我們必須讓節點運行在集群模式下才能開啟一些集群的特性和使用集群的命令。
以下是集群的最小配置文件:
port 7000 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 appendonly yes文件中的 cluster-enabled 選項用于開實例的集群模式, 而 cluster-conf-file 選項則設定了保存節點配置文件的路徑, 默認值為 nodes.conf.節點配置文件無須人為修改, 它由 Redis 集群在啟動時創建, 并在有需要時自動進行更新。
要讓集群正常運作至少需要三個主節點,不過在剛開始試用集群功能時, 強烈建議使用六個節點: 其中三個為主節點, 而其余三個則是各個主節點的從節點。
首先, 讓我們進入一個新目錄, 并創建六個以端口號為名字的子目錄, 稍后我們在將每個目錄中運行一個 Redis 實例:
命令如下:
mkdir cluster-test cd cluster-test mkdir 7000 7001 7002 7003 7004 7005在文件夾 7000 至 7005 中, 各創建一個 redis.conf 文件, 文件的內容可以使用上面的示例配置文件, 但記得將配置中的端口號從 7000 改為與文件夾名字相同的號碼。
從 Redis Github 頁面 的 unstable 分支中取出最新的 Redis 源碼, 編譯出可執行文件 redis-server , 并將文件復制到 cluster-test 文件夾, 然后使用類似以下命令, 在每個標簽頁中打開一個實例:
cd 7000 ../redis-server ./redis.conf你可以從實例打印的日志中看出來, 因為 nodes.conf 文件不存在, 所以每個節點都為它自身指定了一個新的 ID :
[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1實例會一直使用同一個 ID , 從而在集群中保持一個唯一(unique)的名字。每個節點通過這個名字來記憶其他節點,我們把這個字符串稱之為Node ID
創建一個集群
現在我們已經有了六個正在運行中的 Redis 實例, 接下來我們需要使用這些實例來創建集群, 并為每個節點編寫配置文件。
通過使用 Redis 集群命令行工具 redis-trib , 編寫節點配置文件的工作可以非常容易地完成: redis-trib 位于 Redis 源碼的 src 文件夾中, 它是一個 Ruby 程序, 這個程序通過向實例發送特殊命令來完成創建新集群, 檢查集群, 或者對集群進行重新分片(reshared)等工作。
通過使用 Redis 集群命令行工具 redis-trib , 編寫節點配置文件的工作可以非常容易地完成: redis-trib 位于 Redis 源碼的 src 文件夾中, 它是一個 Ruby 程序, 這個程序通過向實例發送特殊命令來完成創建新集群, 檢查集群, 或者對集群進行重新分片(reshared)等工作。
./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這個命令在這里用于創建一個新的集群, 選項--replicas 1 表示我們希望為集群中的每個主節點創建一個從節點。
之后跟著的其他參數則是這個集群實例的地址列表,3個master3個slave
redis-trib 會打印出一份預想中的配置給你看, 如果你覺得沒問題的話, 就可以輸入 yes , redis-trib 就會將這份配置應用到集群當中,讓各個節點開始互相通訊,最后可以得到如下信息:
[OK] All 16384 slots covered這表示集群中的 16384 個槽都有至少一個主節點在處理, 集群運作正常。
用create-cluster腳本搭建redis集群
如果你不想像上面提到的那樣手動配置每個節點,這里提供了一個更簡單的系統(但是你不會學習到那么多的選項)。只需要去查看下redis發布版本中的utils/create-cluster 文件夾。里面有一個叫 create-cluster 的腳本。該腳本可以通過以下命令啟動一個含有6個節點(3master 和 3slave)的集群:
create-cluster start create-cluster create在第2步,redis-trib utility 需要你接受集群方案的時候記得回答yes。
你現在可以跟集群交互了。第一個節點會默認監聽300001。如果你想停集群用以下命令:
create-cluster stop
讓我們開始玩集群吧
現階段redis集群有一個問題,那就是缺乏客戶端庫。
以下是我知道的客戶端實現:- redis-rb-cluster 是我寫的一個ruby客戶端實現(這邊指的是作者@antirez) 。這個庫對原生的 redis-rb 進行了一個簡單的封裝,用最小代碼量實現了高效的對集群的操作。
- redis-py-cluster:這個客戶端用python對redis-rb-cluster進行了轉接。支持大部分 redis-py 的功能。
- Predis : 這個庫最近很活躍,更新很快。基于PHP
- Jedis:最流行的java客戶端,最近也支持redis集群了。你可以在項目的README里面的集群段落看到相關介紹。
- StackExchange.Redis : C# 的客戶端
- thunk-redis:nodejs和io.js 的客戶端
- Redis庫的不穩定分支里面的 redis-cli 工具也提供了非常基本的集群支持。具體使用的方式是用 -c 來啟動該工具可以切換到集群模式。
測試 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 的集群功能只提供了非常基本的功能,所以他總是假定:客戶端知道數據在哪個節點之上,并準確的連接數據所在的節點。但是一個實際使用的客戶端應該應該要緩存哈希槽和節點之間的映射關系,通過這個映射關系來引導客戶單連接指定的節點。該映射關系只有集群配置改變的時候才刷新。比如在一次failover之后或者系統管理員通過增加或者刪除節點來改變集群的分布之后,該映射關系才刷新。
用 redis-rb-cluster 來寫一個例子app
在展示如何使用redis集群做失效備援或者重新分片之前。我們需要建立一些例子工程,或者至少理解一個簡單的redis集群客戶端跟集群交互的一些基本語法。
通過這種方式我們可以運行一個例子,在運行例子的同時我們嘗試讓一些節點掛掉或者啟動一個重新分片過程,以此來觀察redis集群如何響應真實情況下的突發情況的。 如果不寫點什么到集群里面去沒法看出什么門道。
本節將通過兩個示例應用來展示 redis-rb-cluster 的基本用法, 以下是本節的第一個示例應用, 它是一個名為 example.rb 的文件, 包含在redis-rb-cluster 項目里面
1 require './cluster' 2 3 startup_nodes = [ 4 {:host => "127.0.0.1", :port => 7000}, 5 {:host => "127.0.0.1", :port => 7001} 6 ] 7 rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1) 8 9 last = false 10 11 while not last 12 begin 13 last = rc.get("__last__") 14 last = 0 if !last 15 rescue => e 16 puts "error #{e.to_s}" 17 sleep 1 18 end 19 end 20 21 ((last.to_i+1)..1000000000).each{|x| 22 begin 23 rc.set("foo#{x}",x) 24 puts rc.get("foo#{x}") 25 rc.set("__last__",x) 26 rescue => e 27 puts "error #{e.to_s}" 28 end 29 sleep 0.1 30 }
這個應用所做的工作非常簡單: 它不斷地以 foo<number> 為鍵, number 為值, 使用 SET 命令向數據庫設置鍵值對:
- SET foo0 0
- SET foo1 1
- SET foo2 2
- 以此類推
代碼中的每個集群操作都使用一個 begin 和 rescue 代碼塊(block)包裹著, 因為我們希望在代碼出錯時, 將錯誤打印到終端上面, 而不希望應用因為異常(exception)而退出。
代碼的第七行是代碼中第一個有趣的地方, 它創建了一個 Redis 集群對象, 其中創建對象所使用的參數及其意義如下:第一個參數是記錄了啟動節點的 startup_nodes 列表, 列表中包含了兩個集群節點的地址。第二個參數指定了對于集群中的各個不同的節點, Redis 集群對象可以獲得的最大連接數 ,第三個參數 timeout 指定了一個命令在執行多久之后, 才會被看作是執行失敗。
啟動的節點列表不需要包含集群的所有節點。但這些地址中至少要有一個是有效的: 一旦 redis-rb-cluster 成功連接上集群中的某個節點時, 集群節點列表就會被自動更新, 任何真正的的集群客戶端都應該這樣做。
現在, 程序創建的 Redis 集群對象實例被保存到 rc 變量里面, 我們可以將這個對象當作普通 Redis 對象實例來使用。
從第11行到第19行發生了以下事情:我們先嘗試閱讀計數器中的值, 如果計數器不存在的話, 我們才將計數器初始化為 0 : 通過將計數值保存到 Redis 的計數器里面, 我們可以在示例重啟之后, 仍然繼續之前的執行過程, 而不必每次重啟之后都從 foo0 開始重新設置鍵值對。為了讓程序在集群下線的情況下, 仍然不斷地嘗試讀取計數器的值, 我們將讀取操作包含在了一個 while 循環里面, 一般的應用程序并不需要如此小心。
21至30行是程序的主循環, 這個循環負責設置鍵值對, 并在設置出錯時打印錯誤信息。程序在主循環的末尾添加了一個 sleep 調用, 讓寫操作的執行速度變慢, 幫助執行示例的人更容易看清程序的輸出。執行 example.rb 程序將產生以下輸出:
ruby ./example.rb 1 2 3 4 5 6 7 8 9 ^C (我把程序給停了)
這個程序并不是十分有趣, 稍后我們就會看到一個更有趣的集群應用示例, 不過在此之前, 讓我們先使用這個示例來演示集群的重新分片操作。
集群重新分片
現在我們可以來嘗試集群重新分片了。做分片的時候請保持集群運行,這樣如果分片對程序有什么影響你就可以觀察的到了。你也可以考慮將 example.rb 中的 sleep 調用刪掉, 從而讓重新分片操作在近乎真實的寫負載下執行。
重分片意思就是把一些哈希槽從一些節點移動到另一些節點中取。正如我們集群創建的時候那樣做的,重新分片也可以使用redis-trib 工具來做。
./redis-trib.rb reshard 127.0.0.1:7000你只需要指定一個節點就可以了,redis-trib 會自動找到其他的節點。目前 redis-trib 的重新分片只能通過管理功能實現,比如你不能做到從這個節點自動的移動5%的哈希槽到其他節點去(雖然這個功能正在實現中)。所以由這引出了幾個問題,首要的一個就是你究竟想做一次多大范圍的分片?
你想移動多少哈希槽 (從 1 到 16384)?我們嘗試將1000個槽重新分片, 如果 example.rb 程序一直運行著的話, 現在 1000 個槽里面應該有不少鍵了。
除了移動的哈希槽數量之外, redis-trib 還需要知道重新分片的目標, 也即是, 負責接收這 1000 個哈希槽的節點。我會使用第一個master節點: 127.0.0.1:70000 。 但是我需要在實例中指定Node ID。用redis-trib可以輸出節點列表。但是我也可以通過以下命令找到節點ID:
$ redis-cli -p 7000 cluster nodes | grep myself 97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460ok,現在我知道我的目標是節點的ID是97a3a64667477371c4479320d683e4c8db5858b1。
現在需要指定從哪寫節點來移動keys到目標。我輸入的是all ,這樣就會從其他每個master上取一些哈希槽。最后確認后你將會看到每個redis-trib移動的槽的信息,每個key的移動的信息也會打印出來。在重新分片的過程中,你的例子程序是不會受到影響的,你可以停止或者重新啟動多次。
在重新分片結束后你可以通過如下命令檢查集群狀態
./redis-trib.rb check 127.0.0.1:7000所有的節點都會被該操作覆蓋到。不過此時127.0.0.1:7000這個節點會擁有更多的哈希槽,大概會有6461個。
將重新分片操作做成一個腳本
重新分片操作可以做成自動的,這樣我們就不用在交互模式下手動的輸入一個個參數了。可以通過以下的命令行去實現:
./redis-trib.rb reshard <host>:<port> --from <node-id> --to <node-id> --slots --yes如果您經常重分片,那么可以用這個腳本來實現分片自動化。不過當前沒有辦法讓 redis-trib 自動檢測集群的鍵分配,由此來智能判斷是否需要重新分片。該功能未來會加入。
一個更有趣的示例應用
我們在前面使用的示例程序 example.rb 并不是十分有趣, 因為它只是不斷地對集群進行寫入, 但并不檢查寫入結果是否正確。 比如說, 集群可能會錯誤地將 example.rb 發送的所有 SET 命令都改成了 SET foo 42 , 但因為 example.rb 并不檢查寫入后的值, 所以它不會意識到集群實際上寫入的值是錯誤的。
因為這個原因, redis-rb-cluster 項目包含了一個名為 consistency-test.rb 的示例應用, 這個應用比起 example.rb 有趣得多: 它創建了多個計數器(默認為 1000 個), 并通過發送 INCR 命令來增加這些計數器的值。- 每次使用 INCR 命令更新一個計數器時, 應用會記錄下計數器執行 INCR 命令之后應該有的值。 舉個例子, 如果計數器的起始值為 0 , 而這次是程序第 50 次向它發送 INCR 命令, 那么計數器的值應該是 50 。
- 在每次發送 INCR 命令之前, 程序會隨機從集群中讀取一個計數器的值, 并將它與自己記錄的值進行對比, 看兩個值是否相同。
換句話說, 這個程序是一個一致性檢查器(consistency checker): 如果集群在執行 INCR 命令的過程中, 丟失了某條 INCR 命令, 又或者多執行了某條客戶端沒有確認到的 INCR 命令, 那么檢查器將察覺到這一點 —— 在前一種情況中, consistency-test.rb 記錄的計數器值將比集群記錄的計數器值要大; 而在后一種情況中, consistency-test.rb 記錄的計數器值將比集群記錄的計數器值要小。
運行 consistency-test 程序將產生類似以下的輸出:
$ 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) |每行輸出都打印了程序執行的讀取次數和寫入次數, 以及執行操作的過程中因為集群不可用而產生的錯誤數。
如果程序察覺了不一致的情況出現, 它將在輸出行的末尾顯式不一致的詳細情況。
比如說, 如果我們在 consistency-test.rb 運行的過程中, 手動修改某個計數器的值,那么 consistency-test.rb 將向我們報告不一致情況:
$ redis 127.0.0.1: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 |在我們修改計數器值的時候, 計數器的正確值是 114 (執行了 114 次 INCR 命令), 因為我們將計數器的值設成了 0 , 所以 consistency-test.rb 會向我們報告說丟失了 114 個 INCR 命令。
因為這個示例程序具有一致性檢查功能, 所以我們用它來測試 Redis 集群的故障轉移操作。
失效備援(failover)測試
注意:在執行本節操作的過程中, 請一直運行 consistency-test 程序。為了觸發失效備援,我們要做的最簡單的事情(這也是在一個分布式系統中最簡單的一種故障)就是把一個redis進程搞掛,在我們的例子中就是一個master節點進程。
我們可以定義一個集群,并通過以下命令讓其崩潰:
$ 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-11422ok,現在7000,7001和7002 是master節點了。讓我們用 DEBUG SEGFAULT 命令搞掛7002 節點:
$ redis-cli -p 7002 debug segfault Error: Server closed the connection現在我們可以來觀察下 consistency test 的輸出。
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 集群之所以在失效備援的時候會丟失寫請求是因為它使用的是異步復制機制。我在教程開始的時候沒有提到這點:其實丟失寫請求的情況是很少發生的。因為把請求的響應返回給客戶端和發送復制命令給slave這兩件事情幾乎是同時發生的。所以丟失數據的時間窗口非常小。然而非常難發生并不意味這不可能發生。所以這并沒有改變redis集群無法實現強一致性的事實。
我們現在可以查看失效備援之后集群的布局(注意我同時重啟了崩潰的實例,這樣可以把這個節點作為slave重新加入到系統中):
CLUSTER NODES 命令的結果可能看起來很復雜,但是它實際上是很簡單的,并且是一下token的組合:
$ 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 是master節點。7002之前是master節點,現在是7005的一個slave節點。
CLUSTER NODES 命令的結果可能看起來很復雜,但是它實際上是很簡單的,并且是一下token的組合:
- 節點 ID
- ip:port
- flags :例如 master 、 slave 、 myself 、fail
- 如果節點是一個從節點的話, 那么跟在 flags 之后的將是主節點的節點 ID
- 集群最近一次向節點發送 PING 命令之后, 過去了多長時間還沒接到回復。
- 節點最近一次返回 PONG 回復的時間。
- 節點的配置紀元(configuration epoch):詳細信息請參考 Redis 集群規范 。
- 本節點的網絡連接情況:例如 connected 。
- 節點目前包含的槽:例如 127.0.0.1:7001 目前包含號碼為 5960 至 10921 的哈希槽。
手動失效備援
有時候就算master不是真的出問題了,也需要強制引發一次失效備援。比如為了升級Redis中的一個master節點,又想盡量減小對系統可用性的影響,我們就可以用失效備援來把這個master節點轉換為slave節點。
redis集群可以用CLUSTER FAILOVER命令來進行手動失效備援。這個命令必須要在你想切換的目標slave上執行。
手動失效備援比master實際出錯引發的失效備援更安全。因為手動失效備援是在系統正常運行并且新的master節點持續不斷的接收來自master的復制請求的情況下將客戶端的連接從原來的master移動到新的master之上的,這樣可以保證在切換的過程中不丟失數據。
以下是你做失效備援的時候slave產生的日志:
# 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.基本上之前客戶端連接的那個master已經被我們用失效備援停止了。與此同時,master節點發送跟slave之間的復制位移量(就是現在還差多少沒有復制)。slave會停止下來等待復制位移量被消除。當達到復制位移量的時候才開始失效備援,然后舊master被告知要進行配置切換。當客戶端從舊master解鎖的時候他們已經重定向到新master節點了。
添加新節點
添加一個新節點其實就是以下過程:添加一個空白節點,移動一些數據到這個節點里面或者告訴它作為一個現有節點的備份節點,即一個slave節點。
本節將對以上兩種情況進行介紹,首先介紹主節點的添加方法:2種情況第一個步驟都是添加一個空白節點。
照啟動其他節點的配置(我們已經照這個配置啟動了7000到7005這6個節點了)來啟動一個監聽7006的節點其實很簡單,唯一不一樣的就是端口號.以下是啟動端口號為 7006 的新節點的詳細步驟:
- 在終端里創建一個新的標簽頁。
- 進入 cluster-test 文件夾。
- 創建并進入 7006 文件夾。
- 將 redis.conf 文件復制到 7006 文件夾里面,然后將配置中的端口號選項改為 7006 。
- 使用命令 ../../redis-server redis.conf 啟動節點。
如果一切正常, 那么節點應該會正確地啟動.
現在我們像平時一樣使用 redis-trib 來添加一個節點到集群中
./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000正如你所見我使用 add-node 命令的時候第1個參數用來指定新節點的地址,第2個參數可以隨便使用集群中的任何一個節點。
在實際情況下 redis-trib 其實沒有幫我們做很多事情,它只是發送了一個 CLUSTER MEET 信息給節點,這件事情手動也可以完成。 然而redis-trib 還在操作之前檢查了集群的狀態,所以就算你知道redis-trib 是如何工作的你也最好使用redis-trib來執行這些操作。
通過 cluster nodes 命令, 我們可以確認新節點 127.0.0.1:7006 已經被添加到集群里面了
通過 cluster nodes 命令, 我們可以確認新節點 127.0.0.1:7006 已經被添加到集群里面了
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新節點現在已經連接上了集群, 成為集群的一份子, 并且可以對客戶端的命令請求進行轉向了, 但是和其他主節點相比, 新節點還有兩點區別
- 新節點沒有包含任何數據, 因為它沒有包含任何哈希桶。
- 盡管新節點沒有包含任何哈希桶, 但它仍然是一個主節點, 所以在集群需要將某個從節點升級為新的主節點時, 這個新節點不會被選中。
現在可以用redis-trib 的重新分片功能移動一些哈希槽到這個節點了。因為使用 redis-trib 移動哈希桶的方法在前面已經介紹過, 所以這里就不再重復介紹了。
添加一個slave節點
通過2步操作可以添加一個新的slave。 第一步是再次使用 redis-trib ,不過這回要帶上 --slave 選項,如下:
./redis-trib.rb add-node --slave 127.0.0.1:7006 127.0.0.1:7000注意到這條命令跟我們之前添加master的命令非常像。我們并不用具體指定要添加slave到哪個master。這樣redis-trib 會在有比較少slave的master節點中隨機的找一個master來掛載slave節點。
不過你也可以精確的指定你要掛載這個slave及誒單到哪個master上:
一個更手動添加slave到指定master的方式是:添加一個空節點然后通過 CLUSTER REPLICATE 命令來將其轉化為一個slave節點。這個方法也同樣適用于當一個節點已經是slave節點的時候你想將它轉換為另一個master的slave。
./redis-trib.rb add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006 127.0.0.1:7000這就是我們添加一個slave到指定master的方法。
一個更手動添加slave到指定master的方式是:添加一個空節點然后通過 CLUSTER REPLICATE 命令來將其轉化為一個slave節點。這個方法也同樣適用于當一個節點已經是slave節點的時候你想將它轉換為另一個master的slave。
比如我現在想給127.0.0.1:7005節點添加一個復制節點(就是slave),該節點現在的哈希槽范圍是 11423-16383,Node ID 是 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e。我所要做的只不過是連上新節點(在此之前該節點已經被作為空master節點添加到集群里面了)并執行以下命令:
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... 現在擁有了兩個slave節點,分別是 7002 (之前就有的) 和 7006 (我們現在加上去的)。
移除一個節點
通過 redis-trib 提供的 del-node 命令可以移除一個slave節點:
./redis-trib del-node 127.0.0.1:7000 `<node-id>`可以用集群中隨便一個節點作為第1個參數。第2個參數是你要移除的節點ID.
你也可以用這條命令來移除master節點,但是在移除master節點之前必須確保它是空的。如果你要移除的master節點不是空的,你需要先用重新分片命令來把數據移到其他的節點。另外一個移除master節點的方法是先進行一次手動的失效備援,等它的slave被選舉為新的master,并且它被作為一個新的slave被重新加到集群中來之后再移除它。很明顯,如果你是想要減少集群中的master數量,這種做法沒什么用。在這種情況下你還是需要用重新分片來移除數據后再移除它。
復制遷移
雖然在redis集群中通過以下命令是可以將一個slave節點重新配置為另外一個master的slave:CLUSTER REPLICATE <master-node-id>然而有時候你不想找系統管理員來幫忙,又想自動的將一個復制節點從一個master下移動到另外一個master下。 這種情況下的復制節點的自動重配置被稱為復制遷移。復制遷移可以提升系統的可靠性。
注意: 你可以從 Redis集群手冊 中讀到復制遷移的細節。但是在這篇教程里面我們只介紹大概的思路和究竟你可以從中得到什么好處。
在某種情況下,你想讓集群的復制節點從一個master遷移到另一個master的原因可能是:集群的抗崩潰能力總是跟集群中master 擁有的平均slave數量成正比。
比如,如果一個集群中每個master只有一個slave,當master和slave都掛掉的時候這個集群就崩潰了。因為此時有一些哈希槽無法找到了。雖然網絡分裂會把一堆節點從集群中孤立出來(這樣你一下就會知道集群出問題了),但是其他的更常見的硬件或者軟件的問題并不會在多臺機器上同時發生,所以很可能在你的這個集群(平均每個master只有一個slave)有一個slave在早上4點掛掉,然后他的master在隨后的早上6點掛掉。這樣依然會導致集群崩潰。
比如,如果一個集群中每個master只有一個slave,當master和slave都掛掉的時候這個集群就崩潰了。因為此時有一些哈希槽無法找到了。雖然網絡分裂會把一堆節點從集群中孤立出來(這樣你一下就會知道集群出問題了),但是其他的更常見的硬件或者軟件的問題并不會在多臺機器上同時發生,所以很可能在你的這個集群(平均每個master只有一個slave)有一個slave在早上4點掛掉,然后他的master在隨后的早上6點掛掉。這樣依然會導致集群崩潰。
我們可以通過給每個master都再多加一個slave節點來改進系統的可靠性,但是這樣很昂貴。復制遷移允許只給某些master增加slave。比方說你的集群有20個節點,10個master,每個master都有1個slave。然后你增加3個slave到集群中并把他們分配給某幾個master節點,這樣某些master就會擁有多于1個slave。
當某個master失去了slave的時候,復制遷移可以將slave節點從擁有富余slave的master旗下遷移給沒有slave的master。所以當你的slave在早上4點掛掉的時候,另一個slave會被遷移過來取代它的位置,這樣當master節點在早上5點掛掉的時候,依然有一個slave可以被選舉為master,集群依然可以正常運行。
所以簡而言之你應該了解關于復制遷移的哪些方面?
- 集群在遷移的時候會嘗試去遷移擁有最多slave數量的master旗下的slave。
- 想利用復制遷移特性來增加系統的可用性,你只需要增加一些slave節點給單個master(哪個master節點并不重要)。
- 復制遷移是由配置項cluster-migration-barrier控制的: 你可以從Redis集群提供的默認配置文件 redis.conf 樣例中了解到更多關于復制遷移的知識。
在Redis集群中升級節點
升級一個slave節點非常簡單,因為你只需要停止節點,升級它,然后啟動節點就好了。如果此時這個slave有客戶端在連接也沒關系,在這個slave停止的時候客戶端會被重定向到別的slave去。
升級一個master就有點復雜了,推薦使用以下步驟升級master:
- 使用 CLUSTER FAILOVER 命令來使用手動失效備援,這樣來把master切換為slave
- 等待master切換為slave完成
- 就像你升級普通slave一樣升級它
- 如果你希望剛剛升級好的節點再次作為master在集群中運行,那就再觸發一次手動失效備援讓這個及節點重新成為master
照這些步驟你就可以一個一個的升級集群中的節點了。
遷移到redis集群
希望遷移到redis集群的用戶可能只有一個master節點。他也可能正在使用一個現有的分片設置,該分片設置中的key已經被切分到N個節點去,這些節點是使用客戶端自己實現的或者redis代理實現的一些分片算法。
在這些情況下遷移到redis集群都是很容易的,然而最終要的是如果應用使用了多key操作。以下是三種不同的情況:
- 不使用多key操作或者事務操作或者Lua腳本(涉及到多key)。對key的訪問都是獨立的。
- 使用多key操作,事務或者lua腳本(涉及到多key),但是只作用于相同的哈希槽,即這些key都有一個{...}包裹起來的部分相同。比如以下的多key操作都是在同一個哈希標簽下的:SUNION {user:1000}.foo {user:1000}.bar.
- 使用了多key操作,事務或者lua腳本(涉及到多key),操作的key并沒有相同的哈希標簽。
Redis集群無法處理第3種情況:如果不想用多key操作就要修改一下應用,或者只在相同哈希槽的情況下使用
第一種和第二種情況是適用的,所以我們重點關注前兩種情況。這兩種情況是采用同一個方法解決的:
假設你已經有一些數據了,這些數據本分割到N個節點上,并且如果沒有數據分片的話這個N=1。你可以采用以下步驟將你的數據遷移到redis集群上:
- 停止你的客戶端。目前redis集群還沒有動態遷移功能。
- 通過BGREWRITEAOF 命令生成一個AOF(append only file)文件。并等待該AOF文件生成完畢
- 把AOF文件命名為 aof-1 到 aof-N 。此時你可以停止你的舊實例 (在實際情況下一般會用相同的機器來跑新集群)
- 建一個有N個master節點但沒有slave節點的集群。你可以吃些添加slave。確保你所有的節點都使用AOF。
- 停止所有節點把它們的aof文件替換成之前保存的aof文件,aof-1對應第1節點,aof-n對應第n個節點。
- 重啟你的redis集群。
- 用 redis-trib 命令修復集群,讓key可以被遷移過來
- 最后,用 redis-trib 來檢查你的集群是否遷移成功。
- 重啟客戶端。
還有另外一種方法來把外部數據導入到redis集群里面,就是用 redis-trib import 命令。該命令可以把所有key從一個運行中的實例遷移到redis集群中(這些key會被從源實例中刪除)。不過請注意如果源實例用的是2.8版本該操作可能會很慢,因為2.8版本還沒有實現遷移連接的緩存,所以你可能需要升級源實例到3.x之后重啟你的實例,之后再遷移。
來自:http://blog.csdn.net/nsrainbow/article/details/49032337
來自:http://blog.csdn.net/nsrainbow/article/details/49032337
本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!