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

集群(上)

這篇文檔是對 Redis 集群的介紹,沒有使用復(fù)雜難懂的東西來理解分布式系統(tǒng)的概念。本文提供了如何建立,測試和操作一個集群的相關(guān)指導(dǎo),但沒有涉及在 Redis 集群規(guī)范(參考本系列其他文章,譯者注)中的諸多細節(jié),只是從用戶的視角來描述系統(tǒng)是如何運作的。

注意,如果你打算來一次認(rèn)真的 Redis 集群的部署,更正式的規(guī)范文檔(關(guān)注本系列文章,譯者注)強烈建議你好好讀一讀。

Redis 集群當(dāng)前處于 alpha 階段,如果你發(fā)現(xiàn)任何問題,請聯(lián)系 Redis 郵件列表,或者在 Redis 的 Github 倉庫中開啟一個問題(issue)。

Redis 集群(Redis Cluster)

Redis 集群提供一種運行 Redis 的方式,數(shù)據(jù)被自動的分片到多個 Redis 節(jié)點。

集群不支持處理多個鍵的命令,因為這需要在 Redis 節(jié)點間移動數(shù)據(jù),使得 Redis 集群不能提供像 Redis 單點那樣的性能,在高負(fù)載下會表現(xiàn)得不可預(yù)知。

Redis 集群也提供在網(wǎng)絡(luò)分割(partitions)期間的一定程度的可用性,這就是在現(xiàn)實中當(dāng)一些節(jié)點失敗或者不能通信時能繼續(xù)進行運轉(zhuǎn)的能力。

所以,在實踐中,你可以從 Redis 集群中得到什么呢?

  • 在多個節(jié)點間自動拆分你的數(shù)據(jù)集的能力。
  • 當(dāng)部分節(jié)點正在經(jīng)歷失敗或者不能與集群其他節(jié)點通信時繼續(xù)運轉(zhuǎn)的能力。

Redis 集群的 TCP 端口(Redis Cluster TCP ports)

每個 Redis 集群節(jié)點需要兩個 TCP 連接打開。正常的 TCP 端口用來服務(wù)客戶端,例如 6379,加 10000 的端口用作數(shù)據(jù)端口,在上面的例子中就是 16379。

第二個大一些的端口用于集群總線(bus),也就是使用二進制協(xié)議的點到點通信通道。集群總線被節(jié)點用于錯誤檢測,配置更新,故障轉(zhuǎn)移授權(quán)等等。客戶端不應(yīng)該嘗試連接集群總線端口,而應(yīng)一直與正常的 Redis 命令端口通信,但是要確保在防火墻中打開了這兩個端口,否則 Redis 集群的節(jié)點不能相互通信。

命令端口和集群總線端口的偏移量一直固定為 10000。

注意,為了讓 Redis 集群工作正常,對每個節(jié)點:

  1. 用于與客戶端通信的正常的客戶端通信端口(通常為 6379)需要開放給所有需要連接集群的客戶端以及其他集群節(jié)點(使用客戶端端口來進行鍵遷移)。
  2. 集群總線端口(客戶端端口加 10000)必須從所有的其他集群節(jié)點可達。

如果你不打開這兩個 TCP 端口,你的集群就不會像你期待的那樣去工作。

Redis 集群的數(shù)據(jù)分片(Redis Cluster data sharding)

Redis 集群沒有使用一致性哈希,而是另外一種不同的分片形式,每個鍵概念上是被我們稱為哈希槽(hash slot)的東西的一部分。

Redis 集群有 16384 個哈希槽,我們只是使用鍵的 CRC16 編碼對 16384 取模來計算一個指定鍵所屬的哈希槽。

每一個 Redis 集群中的節(jié)點都承擔(dān)一個哈希槽的子集,例如,你可能有一個 3 個節(jié)點的集群,其中:

  • 節(jié)點 A 包含從 0 到 5500 的哈希槽。
  • 節(jié)點 B 包含從 5501 到 11000 的哈希槽。
  • 節(jié)點 C 包含從 11001 到 16384 的哈希槽。

這可以讓在集群中添加和移除節(jié)點非常容易。例如,如果我想添加一個新節(jié)點 D,我需要從節(jié)點 A,B,C 移動一些哈希槽到節(jié)點 D。同樣地,如果我想從集群中移除節(jié)點 A,我只需要移動 A 的哈希槽到 B 和 C。當(dāng)節(jié)點 A 變成空的以后,我就可以從集群中徹底刪除它。

因為從一個節(jié)點向另一個節(jié)點移動哈希槽并不需要停止操作,所以添加和移除節(jié)點,或者改變節(jié)點持有的哈希槽百分比,都不需要任何停機時間(downtime)。

Redis 集群的主從模型(Redis Cluster master-slave model)

為了當(dāng)部分節(jié)點失效時,或者無法與大多數(shù)節(jié)點通信時仍能保持可用,Redis 集群采用每個節(jié)點擁有 1(主服務(wù)自身)到 N 個副本(N-1 個附加的從服務(wù)器)的主從模型。

在我們的例子中,集群擁有 A,B,C 三個節(jié)點,如果節(jié)點 B 失效集群將不能繼續(xù)服務(wù),因為我們不再有辦法來服務(wù)在 5501-11000 范圍內(nèi)的哈希槽。

但是,如果當(dāng)我們創(chuàng)建集群后(或者稍后),我們?yōu)槊恳粋€主服務(wù)器添加一個從服務(wù)器,這樣最終的集群就由主服務(wù)器 A,B,C 和從服務(wù)器 A1,B1,C1 組成,如果 B 節(jié)點失效系統(tǒng)仍能繼續(xù)服務(wù)。

B1 節(jié)點復(fù)制 B 節(jié)點,于是集群會選舉 B1 節(jié)點作為新的主服務(wù)器,并繼續(xù)正確的運轉(zhuǎn)。

Redis 集群的一致性保證(Redis Cluster consistency guarantees)

Redis 集群不保證強一致性。實踐中,這意味著在特定的條件下,Redis 集群可能會丟掉一些被系統(tǒng)收到的寫入請求命令。

Redis 集群為什么會丟失寫請求的第一個原因,是因為采用了異步復(fù)制。這意味著在寫期間下面的事情發(fā)生了:

  • 你的客戶端向主服務(wù)器 B 寫入。
  • 主服務(wù)器 B 回復(fù) OK 給你的客戶端。
  • 主服務(wù)器 B 傳播寫入操作到其從服務(wù)器 B1,B2 和 B3。

你可以看到,B 在回復(fù)客戶端之前沒有等待從 B1,B2,B3 的確認(rèn),因為這是一個過高的延遲代價,所以如果你的客戶端寫入什么東西,B 確認(rèn)了這個寫操作,但是在發(fā)送寫操作到其從服務(wù)器前崩潰了,其中一個從服務(wù)器被提升為主服務(wù)器,永久性的丟失了這個寫操作。

這非常類似于在大多數(shù)被配置為每秒刷新數(shù)據(jù)到磁盤的數(shù)據(jù)庫發(fā)生的事情一樣,這是一個可以根據(jù)以往不包括分布式系統(tǒng)的傳統(tǒng)數(shù)據(jù)庫系統(tǒng)的經(jīng)驗來推理的場景。同樣的,你可以通過在回復(fù)客戶端之前強制數(shù)據(jù)庫刷新數(shù)據(jù)到磁盤來改進一致性,但這通常會極大的降低性能。

基本上,有一個性能和一致性之間的權(quán)衡。

注意:未來,Redis 集群在必要時可能或允許用戶執(zhí)行同步寫操作。

Redis 集群丟失寫操作還有另一個場景,發(fā)生在網(wǎng)絡(luò)分割時,客戶端與至少包含一個主服務(wù)器的少數(shù)實例被孤立起來了。

舉個例子,我們的集群由 A,B,C,A1,B1,C1 共 6 個節(jié)點組成,3 個主服務(wù)器,3 個從服務(wù)器。還有一個客戶端,我們稱為 Z1。

分割發(fā)生以后,有可能分割的一側(cè)是 A,C,A1,B1,C1,分割的另一側(cè)是 B 和 Z1。

Z1 仍然可以寫入到可接受寫請求的 B。如果分割在很短的時間內(nèi)恢復(fù),集群會正常的繼續(xù)。但是,如果分割持續(xù)了足夠的時間,B1 在分割的大多數(shù)這一側(cè)被提升為主服務(wù)器,Z1 發(fā)送給 B 的寫請求會丟失。

注意,Z1 發(fā)送給 B 的寫操作數(shù)量有一個最大窗口:如果分割的大多數(shù)側(cè)選舉一個從服務(wù)器為主服務(wù)器后過了足夠多的時間,少數(shù)側(cè)的每一個主服務(wù)器節(jié)點將停止接受寫請求。

這個時間量是 Redis 集群一個非常重要的配置指令,稱為節(jié)點超時(node timeout)。

節(jié)點超時時間過后,主服務(wù)器節(jié)點被認(rèn)為失效,可以用其一個副本來取代。同樣地,節(jié)點超時時間過后,主服務(wù)器節(jié)點還不能感知其它主服務(wù)器節(jié)點的大多數(shù),則進入錯誤狀態(tài),并停止接受寫請求。

創(chuàng)建和使用 Redis 集群(Creating and using a Redis Cluster)

要創(chuàng)建一個集群,我們要做的第一件事情就是要有若干運行在集群模式下的 Redis 實例。這基本上意味著,集群不是使用正常的 Redis 實例創(chuàng)建的,而是需要配置一種特殊的模式 Redis 實例才會開啟集群特定的特性和命令。

下面是最小的 Redis 集群配置文件:

port 7000  
cluster-enabled yes  
cluster-config-file nodes.conf  
cluster-node-timeout 5000  
appendonly yes  

正如你所看到的,簡單的 cluster-enabled 指令開啟了集群模式。每個實例包含一個保存這個節(jié)點配置的文件的路徑,默認(rèn)是 nodes.conf。這個文件不會被用戶接觸到,啟動時由 Redis 集群實例生成,每次在需要時被更新。

注意,可以正常運轉(zhuǎn)的最小集群需要包含至少 3 個主服務(wù)器節(jié)點。在你的第一次嘗試中,強烈建議開始一個 6 個節(jié)點的集群,3 個主服務(wù)器,3 個從服務(wù)器。

要這么做,先進入一個新的目錄,創(chuàng)建下面這些以端口號來命名的目錄,我們后面會在每個目錄中運行實例。

像這樣:

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

在從 7000 到 7005 的每個目錄內(nèi)創(chuàng)建一個 redis.conf 文件。作為你的配置文件的模板,只使用上面的小例子,但是要確保根據(jù)目錄名來使用正確的端口號來替換端口號 7000。

現(xiàn)在,復(fù)制你從 Github 的不穩(wěn)定分支的最新的源代碼編譯出來的 redis-server 可執(zhí)行文件到 cluster-test 目錄中,最后在你喜愛的終端應(yīng)用程序中打開 6 個終端標(biāo)簽。

像這樣在每個標(biāo)簽中啟動實例:

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

你可以從每個實例的日志中看到,因為 nodes.conf 文件不存在,每個節(jié)點都為自己賦予了一個新 ID。

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

這個 ID 會一直被這個實例使用,這樣實例就有一個在集群上下文中唯一的名字。每個節(jié)點使用這個 ID 來記錄每個其它節(jié)點,而不是靠 IP 和端口。IP 地址和端口可能會變化,但是唯一的節(jié)點標(biāo)識符在節(jié)點的整個生命周期中都不會改變。我們稱這個標(biāo)識符為節(jié)點 ID(Node ID)。

創(chuàng)建集群(Creating the cluster)

現(xiàn)在,我們已經(jīng)有了一些運行中的實例,我們需要創(chuàng)建我們的集群,寫一些有意義的配置到節(jié)點中。

這很容易完成,因為我們有稱為 redis-trib 的 Redis 集群命令行工具來幫忙,這是一個 Ruby 程序,可以在實例上執(zhí)行特殊的命令來創(chuàng)建一個新的集群,檢查或重分片一個已存在的集群,等等。

redis-trib 工具在 Redis 源代碼分發(fā)版本的 src 目錄中。要創(chuàng)建你的集群,簡單輸入:

./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,因為我們想創(chuàng)建一個新的集群。--replicas 1 選項意思是我們希望每個創(chuàng)建的主服務(wù)器有一個從服務(wù)器。其他參數(shù)是我想用來創(chuàng)建新集群的實例地址列表。

顯然,我們要求的唯一布局就是創(chuàng)建一個擁有 3 個主服務(wù)器和 3 個從服務(wù)器的集群。

Redis-trib 會建議你一個配置。輸入 yes 接受。集群會被配置和連接在一起,也就是說,實例會被引導(dǎo)為互相之間對話。最后,如果一切順利你會看到一個類似這樣的消息:

[OK] All 16384 slots covered  

這表示,16384 個槽中的每一個至少有一個主服務(wù)器在處理。

與集群共舞(Playing with the cluste)

在當(dāng)前階段,Redis 集群的一個問題是缺少客戶端庫的實現(xiàn)。

據(jù)我所知有以下實現(xiàn):

  • redis-rb-cluster 是我(@antirez)寫的 Ruby 實現(xiàn),作為其他語言的參考。這個是對原先的
  • redis-rb 進行了簡單的封裝,實現(xiàn)了與集群高效對話的最小語義。
  • redis-py-cluster 看起來就是 redis-rb-cluster 的 Python 版本。最新沒有更新(最后一次提交是 6 個月之前)但是這是一個起點。
  • 流行的 Predis 有對 Redis 集群的支持,支持最近有更新,并處于活躍開發(fā)狀態(tài)。
  • 最多使用的 Java 客戶端 Jedis 最近增加了對 Redis 集群的支持,請查看項目 README 中的 Jedis 集群部分。
  • StackExchange.Redis 提供對 C#的支持(應(yīng)該與大多數(shù).NET 語言工作正常:VB,F(xiàn)#等)。
  • Github 上 Redis 倉庫的不穩(wěn)定分支上的 redis-cli 工具實現(xiàn)了一個基本的集群支持,使用-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"  

redis-cli 的集群支持非?;荆钥偸且蕾?Redis 集群節(jié)點重定向客戶端到正確的節(jié)點。一個真正的客戶端可以做得更好,緩存哈希槽和節(jié)點地址之間的映射,直接使用到正確節(jié)點的正確連接。映射只在集群的配置發(fā)生某些變化時才重新刷新,例如,故障轉(zhuǎn)移以后,或者系統(tǒng)管理員通過添加或移除節(jié)點改變了集群的布局以后。

上一篇:集中插入下一篇:復(fù)制