在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ 大數(shù)據(jù)/ 集群(中)
使用 Redis 實(shí)現(xiàn) Twitter(上)
集群(下)
使用 Redis 實(shí)現(xiàn) Twitter(下)
使用 Redis 作為 LRU 緩存
高可用(上)
高可用客戶端指引
集群(中)
高可用(下)
持久化
Redis 介紹
集中插入
集群(上)
從入門到精通(上)
從入門到精通(下)
從入門到精通(中)
分片
數(shù)據(jù)類型初探
復(fù)制

集群(中)

使用 redis-rb-cluster 寫一個(gè)示例應(yīng)用

在后面介紹如何操作 Redis 集群之前,像故障轉(zhuǎn)移或者重新分片這樣的事情,我們需要?jiǎng)?chuàng)建一個(gè)示例應(yīng)用,或者至少要了解簡(jiǎn)單的 Redis 集群客戶端的交互語(yǔ)義。

我們采用運(yùn)行一個(gè)示例,同時(shí)嘗試使節(jié)點(diǎn)失效,或者開始重新分片這樣的方式,來(lái)看看在真實(shí)世界條件下 Redis 集群如何表現(xiàn)。如果沒(méi)有人往集群寫的話,觀察集群發(fā)生了什么也沒(méi)有什么實(shí)際用處。

這一小節(jié)通過(guò)兩個(gè)例子來(lái)解釋 redis-rb-cluster 的基本用法。第一個(gè)例子在 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
      }

這個(gè)程序做了一件很簡(jiǎn)單的事情,一個(gè)一個(gè)地設(shè)置形式為 foo<number> 的鍵的值為一個(gè)數(shù)字。所以如果你運(yùn)行這個(gè)程序,結(jié)果就是下面的命令流:

SET foo0 0  
SET foo1 1  
SET foo2 2  
And so forth...  

這個(gè)程序看起來(lái)要比通常看起來(lái)更復(fù)雜,因?yàn)檫@個(gè)是設(shè)計(jì)用來(lái)在屏幕上展示錯(cuò)誤,而不是由于異常退出,所以每一個(gè)對(duì)集群執(zhí)行的操作都被 begin rescue 代碼塊包圍起來(lái)。

第 7 行是程序中第一個(gè)有意思的地方。創(chuàng)建了 Redis 集群對(duì)象,使用啟動(dòng)節(jié)點(diǎn)(startup nodes)的列表,對(duì)象允許的最大連接數(shù),以及指定操作被認(rèn)為失效的超時(shí)時(shí)間作為參數(shù)。 啟動(dòng)節(jié)點(diǎn)不需要是全部的集群節(jié)點(diǎn)。重要的是至少有一個(gè)節(jié)點(diǎn)可達(dá)。也要注意,redis-rb-cluster 一旦連接上了第一個(gè)節(jié)點(diǎn)就會(huì)更新啟動(dòng)節(jié)點(diǎn)的列表。你可以從任何真實(shí)的客戶端中看到這樣的行為。

現(xiàn)在,我們將 Redis 集群對(duì)象實(shí)例保存在 rc 變量中,我們準(zhǔn)備像一個(gè)正常的 Redis 對(duì)象實(shí)例一樣來(lái)使用這個(gè)對(duì)象。

第 11 至 19 行說(shuō)的是:當(dāng)我們重啟示例的時(shí)候,我們不想又從 foo0 開始,所以我們保存計(jì)數(shù)到 Redis 里面。上面的代碼被設(shè)計(jì)為讀取這個(gè)計(jì)數(shù)值,或者,如果這個(gè)計(jì)數(shù)器不存在,就賦值為 0。

但是,注意這里為什么是個(gè) while 循環(huán),因?yàn)槲覀兿爰词辜合戮€并返回錯(cuò)誤也要不斷地重試。一般的程序不必這么小心謹(jǐn)慎。

第 21 到 30 行開始了主循環(huán),鍵被設(shè)置賦值或者展示錯(cuò)誤。

注意循環(huán)最后 sleep 調(diào)用。在你的測(cè)試中,如果你想盡可能快地往集群寫入,你可以移除這個(gè) sleep(相對(duì)來(lái)說(shuō),這是一個(gè)繁忙的循環(huán)而不是真實(shí)的并發(fā),所以在最好的條件下通??梢缘玫矫棵?10k 次操作)。

正常情況下,寫被放慢了速度,讓人可以更容易地跟蹤程序的輸出。

運(yùn)行程序產(chǎn)生了如下輸出:

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

這不是一個(gè)很有趣的程序,稍后我們會(huì)使用一個(gè)更有意思的例子,看看在程序運(yùn)行時(shí)進(jìn)行重新分片會(huì)發(fā)生什么事情。

重新分片集群(Resharding the cluster)

現(xiàn)在,我們準(zhǔn)備嘗試集群重分片。要做這個(gè)請(qǐng)保持 example.rb 程序在運(yùn)行中,這樣你可以看到是否對(duì)運(yùn)行中的程序有一些影響。你也可能想注釋掉 sleep 調(diào)用,這樣在重分片期間就有一些真實(shí)的寫負(fù)載。

重分片基本上就是從部分節(jié)點(diǎn)移動(dòng)哈希槽到另外一部分節(jié)點(diǎn)上去,像創(chuàng)建集群一樣也是通過(guò)使用 redis-trib 工具來(lái)完成。

開啟重分片只需要輸入:

./redis-trib.rb reshard 127.0.0.1:7000  

你只需要指定單個(gè)節(jié)點(diǎn),redis-trib 會(huì)自動(dòng)找到其它節(jié)點(diǎn)。

當(dāng)前 redis-trib 只能在管理員的支持下進(jìn)行重分片,你不能只是說(shuō)從這個(gè)節(jié)點(diǎn)移動(dòng) 5%的哈希槽到另一個(gè)節(jié)點(diǎn)(但是這也很容易實(shí)現(xiàn))。那么問(wèn)題就隨之而來(lái)了。第一個(gè)問(wèn)題就是你想要重分片多少:

你想移動(dòng)多少哈希槽(從 1 到 16384)?

我們嘗試重新分片 1000 個(gè)哈希槽,如果沒(méi)有 sleep 調(diào)用的那個(gè)例子程序還在運(yùn)行的話,這些槽里面應(yīng)該已經(jīng)包含了不少的鍵了。

然后,redis-trib 需要知道重分片的目標(biāo)了,也就是將接收這些哈希槽的節(jié)點(diǎn)。我將使用第一個(gè)主服務(wù)器節(jié)點(diǎn),也就是 127.0.0.1:7000,但是我得指定這個(gè)實(shí)例的節(jié)點(diǎn) ID。這已經(jīng)被 redis-trib 打印在一個(gè)列表中了,但是我總是可以在需要時(shí)使用下面的命令找到節(jié)點(diǎn)的 ID:

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

好了,我的目標(biāo)節(jié)點(diǎn)是 97a3a64667477371c4479320d683e4c8db5858b1。

現(xiàn)在,你會(huì)被詢問(wèn)想從哪些節(jié)點(diǎn)獲取這些鍵。我會(huì)輸入 all,這樣就會(huì)從所有其它的主服務(wù)器節(jié)點(diǎn)獲取一些哈希槽。

在最后的確認(rèn)后,你會(huì)看到每一個(gè)被 redis-trib 準(zhǔn)備從一個(gè)節(jié)點(diǎn)移動(dòng)到另一個(gè)節(jié)點(diǎn)的槽的消息,并且會(huì)為每一個(gè)被從一側(cè)移動(dòng)到另一側(cè)的真實(shí)的鍵打印一個(gè)圓點(diǎn)。

在重分片進(jìn)行的過(guò)程中,你應(yīng)該能夠看到你的示例程序運(yùn)行沒(méi)有受到影響。如果你愿意的話,你可以在重分片期間多次停止和重啟它。

在重分片的最后,你可以使用下面的命令來(lái)測(cè)試一下集群的健康情況:

./redis-trib.rb check 127.0.0.1:7000  

像平時(shí)一樣,所有的槽都會(huì)被覆蓋到,但是這次在 127.0.0.1:7000 的主服務(wù)器會(huì)擁有更多的哈希槽,大約 6461 個(gè)左右。

一個(gè)更有意思的示例程序

到目前為止一切挺好,但是我們使用的示例程序卻不夠好。不顧后果地(acritically)往集群里面寫,而不檢查寫入的東西是否是正確的。

從我們的觀點(diǎn)看,接收寫請(qǐng)求的集群可能一直將每個(gè)操作都作為設(shè)置鍵 foo 值為 42,我們卻根本沒(méi)有察覺(jué)到。

所以在 redis-rb-cluster 倉(cāng)庫(kù)中,有一個(gè)叫做 consistency-test.rb 的更有趣的程序。這個(gè)程序有意思得多,因?yàn)樗褂靡唤M計(jì)數(shù)器,默認(rèn) 1000 個(gè),發(fā)送 INCR 命令來(lái)增加這些計(jì)數(shù)器。

但是,除了寫入,程序還做另外兩件事情:

  • 當(dāng)計(jì)數(shù)器使用 INCR 被更新后,程序記住了寫操作。
  • 在每次寫之前讀取一個(gè)隨機(jī)計(jì)數(shù)器,檢查這個(gè)值是否是期待的值,與其在內(nèi)存中的值比較。

這個(gè)的意思就是,這個(gè)程序就是一個(gè)一致性檢查器,可以告訴你集群是否丟失了一些寫操作,或者是否接受了一個(gè)我們沒(méi)有收到確認(rèn)(acknowledgement)的寫操作。在第一種情況下,我們會(huì)看到計(jì)數(shù)器的值小于我們記錄的值,而在第二種情況下,這個(gè)值會(huì)大于。

運(yùn)行 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ù),以及錯(cuò)誤數(shù)(錯(cuò)誤導(dǎo)致的未被接受的查詢是因?yàn)橄到y(tǒng)不可用)。

如果發(fā)現(xiàn)了不一致性,輸出將增加一些新行。例如,當(dāng)我在程序運(yùn)行期間手工重置計(jì)數(shù)器,就會(huì)發(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)我把計(jì)數(shù)器設(shè)置為 0 時(shí),真實(shí)值是 144,所以程序報(bào)告了 144 個(gè)寫操作丟失(集群沒(méi)有記住的 INCR 命令執(zhí)行的次數(shù))。

這個(gè)程序作為測(cè)試用例很有意思,所以我們會(huì)使用它來(lái)測(cè)試 Redis 集群的故障轉(zhuǎn)移。

測(cè)試故障轉(zhuǎn)移(Testing the failover)

注意:在測(cè)試期間,你應(yīng)該打開一個(gè)標(biāo)簽窗口,一致性檢查的程序在其中運(yùn)行。

為了觸發(fā)故障轉(zhuǎn)移,我們可以做的最簡(jiǎn)單的事情(這也是能發(fā)生在分布式系統(tǒng)中語(yǔ)義上最簡(jiǎn)單的失?。┚褪亲屢粋€(gè)進(jìn)程崩潰,在我們的例子中就是一個(gè)主服務(wù)器。

我們可以使用下面的命令來(lái)識(shí)別一個(gè)集群并讓其崩潰:

$ 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 命令來(lái)使節(jié)點(diǎn) 7002 崩潰:

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

現(xiàn)在,我們可以看看一致性測(cè)試的輸出報(bào)告了些什么內(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 個(gè)讀請(qǐng)求和 577 個(gè)寫請(qǐng)求,但是數(shù)據(jù)庫(kù)中沒(méi)有產(chǎn)生不一致性。這聽起來(lái)好像和我們?cè)谶@篇教程的第一部分中陳述的不一樣,我們說(shuō)道,Redis 集群在故障轉(zhuǎn)移期間會(huì)丟失寫操作,因?yàn)樗褂卯惒綇?fù)制。但是我們沒(méi)有說(shuō)過(guò)的是,這并不是經(jīng)常發(fā)生,因?yàn)?Redis 發(fā)送回復(fù)給客戶端,和發(fā)送復(fù)制命令給從服務(wù)器差不多是同時(shí),所以只有一個(gè)很小的丟失數(shù)據(jù)窗口。但是,很難觸發(fā)并不意味著不可能發(fā)生,所以這并沒(méi)有改變 Redis 集群提供的一致性保證(即非強(qiáng)一致性,譯者注)。

我們現(xiàn)在可以看看故障轉(zhuǎn)移后的集群布局(注意,與此同時(shí),我重啟了崩潰的實(shí)例,所以它以從服務(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ù)器運(yùn)行在 7000,7001 和 7005 端口。之前運(yùn)行在 7002 端口的主服務(wù)器現(xiàn)在是 7005 的從服務(wù)器了。

CLUSTER NODES 命令的輸出看起來(lái)挺可怕的,但是實(shí)際上相當(dāng)?shù)暮?jiǎn)單,由以下部分組成:

  • 節(jié)點(diǎn) ID
  • ip:port
  • flags: master, slave, myself, fail, ...
  • 如果是從服務(wù)器的話,就是其主服務(wù)器的節(jié)點(diǎn) ID
  • 最近一次發(fā)送 PING 后等待回復(fù)的時(shí)間
  • 最近一次發(fā)送 PONG 的時(shí)間
  • 節(jié)點(diǎn)的配置紀(jì)元(請(qǐng)看集群規(guī)范)
  • 節(jié)點(diǎn)的連接狀態(tài)
  • 服務(wù)的哈希槽