在后面介紹如何操作 Redis 集群之前,像故障轉(zhuǎn)移或者重新分片這樣的事情,我們需要創(chuàng)建一個示例應(yīng)用,或者至少要了解簡單的 Redis 集群客戶端的交互語義。
我們采用運行一個示例,同時嘗試使節(jié)點失效,或者開始重新分片這樣的方式,來看看在真實世界條件下 Redis 集群如何表現(xiàn)。如果沒有人往集群寫的話,觀察集群發(fā)生了什么也沒有什么實際用處。
這一小節(jié)通過兩個例子來解釋 redis-rb-cluster 的基本用法。第一個例子在 redis-rb-cluster 發(fā)行版本的 exemple.rb 文件中,如下:
require './cluster'
startup_nodes = [
{:host => "127.0.0.1", :port => 7000},
{:host => "127.0.0.1", :port => 7001}
]
rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1)
last = false
while not last
begin
last = rc.get("__last__")
last = 0 if !last
rescue => e
puts "error #{e.to_s}"
sleep 1
end
end
((last.to_i+1)..1000000000).each{|x|
begin
rc.set("foo#{x}",x)
puts rc.get("foo#{x}")
rc.set("__last__",x)
rescue => e
puts "error #{e.to_s}"
end
sleep 0.1
}
這個程序做了一件很簡單的事情,一個一個地設(shè)置形式為 foo<number>
的鍵的值為一個數(shù)字。所以如果你運行這個程序,結(jié)果就是下面的命令流:
SET foo0 0
SET foo1 1
SET foo2 2
And so forth...
這個程序看起來要比通??雌饋砀鼜?fù)雜,因為這個是設(shè)計用來在屏幕上展示錯誤,而不是由于異常退出,所以每一個對集群執(zhí)行的操作都被 begin rescue 代碼塊包圍起來。
第 7 行是程序中第一個有意思的地方。創(chuàng)建了 Redis 集群對象,使用啟動節(jié)點(startup nodes)的列表,對象允許的最大連接數(shù),以及指定操作被認(rèn)為失效的超時時間作為參數(shù)。 啟動節(jié)點不需要是全部的集群節(jié)點。重要的是至少有一個節(jié)點可達(dá)。也要注意,redis-rb-cluster 一旦連接上了第一個節(jié)點就會更新啟動節(jié)點的列表。你可以從任何真實的客戶端中看到這樣的行為。
現(xiàn)在,我們將 Redis 集群對象實例保存在 rc 變量中,我們準(zhǔn)備像一個正常的 Redis 對象實例一樣來使用這個對象。
第 11 至 19 行說的是:當(dāng)我們重啟示例的時候,我們不想又從 foo0 開始,所以我們保存計數(shù)到 Redis 里面。上面的代碼被設(shè)計為讀取這個計數(shù)值,或者,如果這個計數(shù)器不存在,就賦值為 0。
但是,注意這里為什么是個 while 循環(huán),因為我們想即使集群下線并返回錯誤也要不斷地重試。一般的程序不必這么小心謹(jǐn)慎。
第 21 到 30 行開始了主循環(huán),鍵被設(shè)置賦值或者展示錯誤。
注意循環(huán)最后 sleep 調(diào)用。在你的測試中,如果你想盡可能快地往集群寫入,你可以移除這個 sleep(相對來說,這是一個繁忙的循環(huán)而不是真實的并發(fā),所以在最好的條件下通??梢缘玫矫棵?10k 次操作)。
正常情況下,寫被放慢了速度,讓人可以更容易地跟蹤程序的輸出。
運行程序產(chǎn)生了如下輸出:
ruby ./example.rb
1
2
3
4
5
6
7
8
9
^C (I stopped the program here)
這不是一個很有趣的程序,稍后我們會使用一個更有意思的例子,看看在程序運行時進(jìn)行重新分片會發(fā)生什么事情。
現(xiàn)在,我們準(zhǔn)備嘗試集群重分片。要做這個請保持 example.rb 程序在運行中,這樣你可以看到是否對運行中的程序有一些影響。你也可能想注釋掉 sleep 調(diào)用,這樣在重分片期間就有一些真實的寫負(fù)載。
重分片基本上就是從部分節(jié)點移動哈希槽到另外一部分節(jié)點上去,像創(chuàng)建集群一樣也是通過使用 redis-trib 工具來完成。
開啟重分片只需要輸入:
./redis-trib.rb reshard 127.0.0.1:7000
你只需要指定單個節(jié)點,redis-trib 會自動找到其它節(jié)點。
當(dāng)前 redis-trib 只能在管理員的支持下進(jìn)行重分片,你不能只是說從這個節(jié)點移動 5%的哈希槽到另一個節(jié)點(但是這也很容易實現(xiàn))。那么問題就隨之而來了。第一個問題就是你想要重分片多少:
你想移動多少哈希槽(從 1 到 16384)?
我們嘗試重新分片 1000 個哈希槽,如果沒有 sleep 調(diào)用的那個例子程序還在運行的話,這些槽里面應(yīng)該已經(jīng)包含了不少的鍵了。
然后,redis-trib 需要知道重分片的目標(biāo)了,也就是將接收這些哈希槽的節(jié)點。我將使用第一個主服務(wù)器節(jié)點,也就是 127.0.0.1:7000
,但是我得指定這個實例的節(jié)點 ID。這已經(jīng)被 redis-trib 打印在一個列表中了,但是我總是可以在需要時使用下面的命令找到節(jié)點的 ID:
$ redis-cli -p 7000 cluster nodes | grep myself
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460
好了,我的目標(biāo)節(jié)點是 97a3a64667477371c4479320d683e4c8db5858b1。
現(xiàn)在,你會被詢問想從哪些節(jié)點獲取這些鍵。我會輸入 all,這樣就會從所有其它的主服務(wù)器節(jié)點獲取一些哈希槽。
在最后的確認(rèn)后,你會看到每一個被 redis-trib 準(zhǔn)備從一個節(jié)點移動到另一個節(jié)點的槽的消息,并且會為每一個被從一側(cè)移動到另一側(cè)的真實的鍵打印一個圓點。
在重分片進(jìn)行的過程中,你應(yīng)該能夠看到你的示例程序運行沒有受到影響。如果你愿意的話,你可以在重分片期間多次停止和重啟它。
在重分片的最后,你可以使用下面的命令來測試一下集群的健康情況:
./redis-trib.rb check 127.0.0.1:7000
像平時一樣,所有的槽都會被覆蓋到,但是這次在 127.0.0.1:7000
的主服務(wù)器會擁有更多的哈希槽,大約 6461 個左右。
到目前為止一切挺好,但是我們使用的示例程序卻不夠好。不顧后果地(acritically)往集群里面寫,而不檢查寫入的東西是否是正確的。
從我們的觀點看,接收寫請求的集群可能一直將每個操作都作為設(shè)置鍵 foo 值為 42,我們卻根本沒有察覺到。
所以在 redis-rb-cluster 倉庫中,有一個叫做 consistency-test.rb 的更有趣的程序。這個程序有意思得多,因為它使用一組計數(shù)器,默認(rèn) 1000 個,發(fā)送 INCR 命令來增加這些計數(shù)器。
但是,除了寫入,程序還做另外兩件事情:
這個的意思就是,這個程序就是一個一致性檢查器,可以告訴你集群是否丟失了一些寫操作,或者是否接受了一個我們沒有收到確認(rèn)(acknowledgement)的寫操作。在第一種情況下,我們會看到計數(shù)器的值小于我們記錄的值,而在第二種情況下,這個值會大于。
運行 consistency-test 程序每秒鐘產(chǎn)生一行輸出:
$ 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) |
每一行展示了執(zhí)行的讀操作和寫操作的次數(shù),以及錯誤數(shù)(錯誤導(dǎo)致的未被接受的查詢是因為系統(tǒng)不可用)。
如果發(fā)現(xiàn)了不一致性,輸出將增加一些新行。例如,當(dāng)我在程序運行期間手工重置計數(shù)器,就會發(fā)生:
$ 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 |
當(dāng)我把計數(shù)器設(shè)置為 0 時,真實值是 144,所以程序報告了 144 個寫操作丟失(集群沒有記住的 INCR 命令執(zhí)行的次數(shù))。
這個程序作為測試用例很有意思,所以我們會使用它來測試 Redis 集群的故障轉(zhuǎn)移。
注意:在測試期間,你應(yīng)該打開一個標(biāo)簽窗口,一致性檢查的程序在其中運行。
為了觸發(fā)故障轉(zhuǎn)移,我們可以做的最簡單的事情(這也是能發(fā)生在分布式系統(tǒng)中語義上最簡單的失?。┚褪亲屢粋€進(jìn)程崩潰,在我們的例子中就是一個主服務(wù)器。
我們可以使用下面的命令來識別一個集群并讓其崩潰:
$ 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 都是主服務(wù)器。我們使用 DEBUG SEGFAULT 命令來使節(jié)點 7002 崩潰:
$ redis-cli -p 7002 debug segfault
Error: Server closed the connection
現(xiàn)在,我們可以看看一致性測試的輸出報告了些什么內(nèi)容。
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) |
你可以看到,在故障轉(zhuǎn)移期間,系統(tǒng)不能接受 578 個讀請求和 577 個寫請求,但是數(shù)據(jù)庫中沒有產(chǎn)生不一致性。這聽起來好像和我們在這篇教程的第一部分中陳述的不一樣,我們說道,Redis 集群在故障轉(zhuǎn)移期間會丟失寫操作,因為它使用異步復(fù)制。但是我們沒有說過的是,這并不是經(jīng)常發(fā)生,因為 Redis 發(fā)送回復(fù)給客戶端,和發(fā)送復(fù)制命令給從服務(wù)器差不多是同時,所以只有一個很小的丟失數(shù)據(jù)窗口。但是,很難觸發(fā)并不意味著不可能發(fā)生,所以這并沒有改變 Redis 集群提供的一致性保證(即非強一致性,譯者注)。
我們現(xiàn)在可以看看故障轉(zhuǎn)移后的集群布局(注意,與此同時,我重啟了崩潰的實例,所以它以從服務(wù)器的身份重新加入了集群):
$ 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 connect
現(xiàn)在,主服務(wù)器運行在 7000,7001 和 7005 端口。之前運行在 7002 端口的主服務(wù)器現(xiàn)在是 7005 的從服務(wù)器了。
CLUSTER NODES 命令的輸出看起來挺可怕的,但是實際上相當(dāng)?shù)暮唵危梢韵虏糠纸M成: