Redis集群明細文檔
Redis目前版本是沒有提供集群功能的,如果要實現多臺Redis同時提供服務只能通過客戶端自身去實現(Memchached也是客戶端實現分布式)。目前根據文檔已經看到Redis正在開發集群功能,其中一部分已經開發完成,但是具體什么時候可以用上,還不得而知。文檔來源:http://redis.io/topics/cluster-spec
一、介紹
該文檔是開發之中的redis集群實現細節。該文檔分成兩個部分,第一部分為在redis非穩定版本代碼分支上已經實現的,另外一部分為還需要去實現的。在未來若集群實現設計變更這些都可能被修改,但是相對來說,未實現的部分相較于已經實現的部分被修改的可能性更大些。該文檔包括了實現客戶端需要的各種細節,但是客戶端作者需要注意這些細節都有可能被修改。
二、什么是Redis集群
集群是獨立服務器關于分布式與容錯實現的一個子集。在集群之中沒有中心節點與代理節點,設計的主要目的之一就是線性可伸縮的擴展(即隨意增刪節點)。集群為了保證數據的一致性而犧牲容錯性,所以當網絡故障和節點發生故障時這個系統會盡力去保證數據的一致性和有效性。(這里我們認為節點故障是網絡故障的一種特殊情況)
為了解決單點故障的問題,我們同時需要masters 和 slaves。 即使主節點(master)和從節點(slave)在功能上是一致的,甚至說他們部署在同一臺服務器上,從節點也僅用以替代故障的主節點(即備節點不會被使用除非主節點發生故障而用來代替主節點)。 實際上應該說 如果對從節點沒有read-after-write(寫并立即讀取數據 以免在數據同步過程中無法獲取數據)的需求,那么從節點僅接受只讀操作。
三、已經實現的子集
集群實現了在非分布式版本上的所有單個命令。復雜的多命令操作例如集合sets的交集并集還沒有實現。通常情況下理論上對于不在同一個節點上的操作不會被實現。將來可能實現一種新的節點類型叫做計算節點,用于以只讀的方式在集群上進行多關鍵字的處理,但是不太可能集群中的節點自己通過某種移動關鍵字的方式處理復雜的多關鍵字問題。
集群中的節點不像獨立服務器那樣支持多個數據庫,將會只有數據庫0,并且不支持select命令。
四、集群協議中的客戶端與服務器角色
集群中的節點負責持有數據,知道集群的狀態,包括映射鍵到正確的節點。集群節點能夠自動發現其他節點,檢測到不工作的節點,并且在必要是時候執行備節點提升為主節點的操作。
為了執行任務,所有的節點都被使用一個TCP-BUS連(連接總線),并且執行一個二進制的協議,每一個節點都使用連接總線連接到集群中的其他節點。節點為了發現新節點使用一個傳播協議用于擴散信息,發送ping消息以確認其他節點是否正常工作,并且發送集群消息需要特定的信號條件。集群連接總線也在擴展PUB/SUB信息時被使用。
當集群中的節點不能滿足客戶端的請求時,可能會使用MOVED、ASKED命令來告知重定向。理論上客戶端允許向集群中的任意節點發送請求,如果需要時會得到重定向應答,因此客戶端不需要知道集群的狀態信息。然而客戶端可以緩存keys與節點的關系以改善執行性能。
五、關鍵字分布式模型
關鍵字空間被分割成4096個槽,實際上設置了集群最大節點數為4096個。然后建議的最大值為小幾百個節點。所有的主節點將處理4096個百分比的slot。當集群穩定時(即沒有正在轉移某個slot到另外一個節點),則某個slot必定只被某個節點處理,然后某個節點可以同時處理多個槽。
映射鍵值到指定槽值的算法如下:
HASH_SLOT = CRC16(key) mod 4096
在該文檔的附注1之中有CRC16算法介紹。
使用12滿分的CRC16的16位輸出,在我們的測試之中CRC16能夠很好的將各種類型的key映射到4096的空間之中。
六、集群節點的屬性
在集群中的每個節點都有其在集群中唯一的ID,其ID為160比特隨機數的十六進制表示。節點首次啟動時即獲取ID,節點將獲取其ID并保存在其配置文件之中,并且將一直使用該ID,直到該配置文件被系統管理員刪除。
節點的ID作為集群中的節點識別,一個節點可能修改IP或地址但是不必須修改節點名稱。集群也能夠檢測到節點IP、PORT的變化然后通過連接總線發送變更的協議通知。
每個節點都有一些相關的信息,被其他節點所知道:
1)該節點的IP地址與端口
2)一些標志位
3)該節點服務的key-slot
4)最后一次通過集群連接總線發送ping的時間
5)最后一次收到pong的時間
6)該節點的備份節點數
7)如果其為備份節點,則其主節點的ID(若該節點為主節點則該值為0000000
通過CLUSTER NODES命令可以獲取該集群中的所有節點信息,包括主節點與備份節點。
如下為一個示例:
$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 :0 myself - 0 1318428930 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 connected 2730-4095
上述顯示各項信息依次為:ID、IP:PORT、 FLAGS、最近發送PING的時間,最近接受到PONG時間、連接狀態、slots
七、節點間的握手(已實現)
節點總是接受來自集群總線的端口連接請求,并且在收到ping請求時總是回復,即使ping的源節點非可信的。然而若發送端的節點非所在集群,則其消息將被丟棄。
一個節點接受另一個節點為集群的一部分有如下兩種情況:
1)假如一個節點使用一個MEET消息介紹自己。MEET消息很像一個PING消息,但是其要求接受者將其接受為集群的一部分。節點只有在收到管理員執行的CLUSTER MEET ip port 命令之后才會發送MEET消息到其他節點
2)當一個節點已經被信任時,另一個節點也可以注冊其為該集群的一部分,將擴展該消息到其他節點。假如:A知道B,B知道C,最后B將傳播伙伴信息到A與C,這時A將注冊C為該網絡的一部分,并且試圖連接到C。
這意味著,只要我們加入節點到任意連通圖,他們最終將自動實現全連接。該項意味著集群可以自動發現新節點,但是信任關系需要有管理員設定。這種方式使得集群更加強大并且確保集群不會因為IP或PORT變更而導致多個集群互相混淆。
所有的節點將試圖連接已知的所有其他節點。
八、移動重定向
一個redis客戶端可以自由地將請求發送給集群中的任意一個節點,包括哪些slave節點。節點將分析查詢請求,如果不能接受將通過哈希算法計算該關鍵字歸屬的節點,如果計算出的關鍵字屬于自己處理,則查詢直接被處理,其他情況將根據該節點的持有的其他節點信息,計算出關鍵字哈希然后返回客戶端表示MOVED錯誤。
MOVED錯誤,看起來如下:
GET x
-MOVED 3999 127.0.0.1:6381
錯誤信息中包括了該key哈希出來的slot,以及服務于該slot的節點地址信息,客戶端需要重新請求到指定地址的節點。注意,假如客戶端得到該信息后很長一段時間沒有發起請求,假如集群在此時重組了各個節點服務的slot,則再次請求時有可能再次接收到MOVED錯誤。
因此根據集群節點的視圖以ID為標示來說,我們試圖只是簡單的暴露哈希slot與節點IP,port之間的映射關系。客戶端不必須但是應該試著緩存slot3999是由 127.0.0.1 6381節點提供服務。
這種方式當有一個新的命令需要處理時,就可以計算其slot然后請求到特定的節點能夠增加成功率。
當集群服務穩定后,最終所有的客戶端都有一張slot與節點的映射表,客戶端直接請求到正確的節點而不需要重定向,有助于提高集群的效率。
客戶端應該也能夠處理本文檔描述的ASK命令的重定向。
九、集群動態重構
集群支持在運行時動態增加與刪除節點,事實上對于增加或刪除節點執行的是相同操作,即將處理key的slot從一個節點移動到另一個節點。
1)增加一個新節點時,有一個空的節點添加到集群之中,同時會有一些哈希slot從現存的節點移動到新的節點上
2)刪除一個節點,同樣的將其處理的哈希slot移動到其他節點之上
因此實現的核心是能夠移動哈希slot,實際上,哈希slot對應的就是一組關鍵字key,因此集群在重新哈希時其實就是將一些key從一個節點移動到另一個節點。
要理解該內容,可以通過CLUSTER子命令來手動移動節點的哈希slot,有如下命令:
CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node
前面兩個命令 ADDSLOTS、DELSLOTS只是簡單的從某個節點上增加刪除其處理的slot,當哈希slot分配完成后他們將通過集群協議將信息廣播到所有節點。ADDSLOTS通常用于從零配置一個集群的快速方法。SETSLOTS用于將某個哈希slot分配給指定的已存在的節點。其他情況哈希slot可以通過另外兩種方法設置:MIGRATING、INPORTING
1)當一個哈希slot被設置成MIGRATING,該節點會接受所有對該哈希slot的查詢,但是必須是該key已經存在。其他情況,將指引一個ASK重定向到MIGRATING的目標節點
2)當一個哈希slot被設置成IMPORTTING,該節點會接受所有對該哈希slot的查詢,但是前提是必須執行一個ASKING命令。其他情況若客戶端沒有提供ASKING命令,則請求會被重定向到真實的服務該slot的節點上,產生一個MOVED錯誤。
如此操作可能會奇怪,接下來會詳細說明。假定現在又兩個節點A、B,現在希望將slot 8從節點A移動到節點B,我們使用如下命令:
We send B: CLUSTER SETSLOT 8 IMPORTING A
We send A: CLUSTER SETSLOT 8 MIGRATING B
此時,所有其他節點都會將查詢slot 8的請求定向到A,因此發生如下情況:
1)所有已經存在的key查詢請求都在A中處理
2)而所有未存在的key查詢請求都在B中處理
這種方式我們不會在A中創建新的key,一個特殊的客戶端redis_trib作為集群的重配置將已經存在A中的key遷移到B之中,有如下命令執行:
CLUSTER GETKEYSINSLOT slot count
該命令返回某個slot中的key數量,對于每一個返回的key都會執行一個MIGRATE命令,該命令自動的將key從A遷移到B,這兩種情況下都將鎖定key因此沒有競爭條件。
MIGRATE target_host target_port key target_database id timeout
MIGRATE命令連接到目標實例,然后發送key的數據,當返回OK就從原有的數據庫中刪除該key,因此從外部客戶端看來同一時間key只存在于A或者B。
在redis集群中不需要指定而外的數據庫除了0,但是遷移MIGRATE命令可以用于非集群環境中,是一個通用的命令。遷移MIGRATE命令最優化執行遷移復雜的key例如lists。但是重新配置集群不是一個明智的操作特別是在應用程序有時間限制的時候。
十、ASK重定向
在上一節我們簡單談及ASK重定向,為什么我們能夠簡單的使用MOVED重定向?因為MOVED意味著我們想要某個哈希slot永遠的由另一個節點提供服務,并且后續的查詢應該和試圖到特定的節點上。ASK只要求后續的查詢到特定的節點上。之所以需要如此是由于下次查詢slot 8的可能依然在A上,因此我們希望客戶端嘗試A之后如果需要再查詢B。由于只發生于4096分之一因此執行性能是可以接受的。
然而我們需要確保客戶端在試圖查詢A之后才查詢B,因此B對于設置為IMPORTING的slot的查詢只接受預先發送ASKING命令的查詢。簡單地ASKING命令用于設置一個標志位使得節點能夠為設置為IMPORTING的slot提供服務。
因此對于ASK重定向,客戶端語義如下:
1)若在發送查詢特定節點時收到ASK重定向信息
2)使用ASKING請求作為命令的開始
3)當前不需要修改本地的緩存將slot 8指向B
當哈希slot 8遷移完成時,A將發送一個MOVED錯誤,此時客戶端可以將slot 8查詢緩存定向到B。即使客戶端過早的將slot 8緩存指向B,當想B查詢時沒有發送ASKING開頭,則B將會返回一個MOVED錯誤指向A。
十一、客戶端需要實現
TODO Pipelining: use MULTI/EXEC for pipelining.
TODO Persistent connections to nodes.
TODO hash slot guessing algorithm.
容錯機制
十二、節點的錯誤檢測
錯誤檢查使用如下機制:
1)在一段時間內節點未響應發出的PING消息,則將其設置為PFAIL(可能錯誤)狀態。
2)當ping其他節點時隨機帶上其他三個節點的信息,在信息的gossip節部分帶上其他節點的flag
3)假如有一個節點設置為PFAIL,并且在收到的PING回復中其他節點也將其設置為PFAIL則將其設置為FAIL狀態
4)當一個節點確認某個另外節點為FAIL時則發送消息到所有其他節點,強制收到該消息的節點將其設置為FAIL狀態
因此一個節點在沒有獲取外面消息時無法獨立地將某個另外節點設置為FAIl
仍然需要實現:當一個節點被標記為FAIL,當其他節點收到該節點的請求或連接時,將回復其“MARK AS FAIL”,當收到該消息時需要強制將自身設置為FAIL狀態。
十三、集群狀態檢查(部分實現)
當集群發生變更時(更新slot或者一個節點被設置為fail等)每個節點都將檢查其保持的節點列表。
一旦配置節點進入如下的階段:
1)FAIL:集群不能工作,當一個節點進入該狀態,所有的請求將被拒絕并且返回一個錯誤。當節點檢查到集群不能同時為4096個slot提供服務時進入該狀態
2)OK:集群工作正常:所有的4096個slot都能夠被節點服務到并且沒有FAIL節點
這意味著集群在不能為所有的4096個slot提供服務時停止工作。然而有一部分時間一個slot不能被訪問因為相關聯的節點有問題,但是該節點還未被設置為FAIL。在此時集群只能為其中的部分slot提供訪問服務。
由于集群不支持MULTI、EXEC執行命令,因此程序員需要確保應用程序能夠從只有一部分查詢被集群接受的狀態中恢復。
十四、備節點選擇(未實現)
每一個主節點都能夠有任意數量的從節點。從節點負責在主節點fail時設置自己為主節點。舉例:我們有節點A1,A2,A3其中A1為主節點,A2,A3為從節點。假如A在某個階段FAIL并且沒有響應ping請求,最終其他節點通過gossip協議會將其設置為fail,當發生這種情況時,它的第一個從節點需要嘗試執行選擇。第一個從節點的概念非常簡單。所有的從節點根據node id進行排序,最小的就為第一個從節點,依次類推,當第一從節點也被標記為fail,在后續節點執行該選擇未主節點,以此類推。
當一個配置變更時,錯有從節點檢查自身主節點是否為fail,若是則變更自身狀態為主節點并發送到所有的其他節點以變更配置。
十五、保護模式(未實現)
當網絡不連通導致有孤立節點時,該節點會認為所有其他節點都為fail。這種時候他可能試圖選擇從節點或者更改集群配置。為了避免這種情況,節點認定其他主要節點為pfail或fail要足夠長的時間以避免其采取其他動作。
保護模式被清除之后,集群狀態就恢復正常了。
十六、主要的主節點規則
作為網絡分離的結果之一,兩個或多個分割的部分可能為所有的哈希slot提供服務。由于集群努力保持一致性,這種情況不是我們想要的,而且網絡分割總是產生零個或單個可供操作的部分。如果他們有大部分原來的主節點,當這些規則節點被放到一個部分之中他們應該只提供查詢服務。
十七、分發與訂閱(未實現需要重定義)
在一個集群中的客戶端能夠訂閱任何一個節點,也能夠發布到任何一個節點。集群將確保若需要能夠得到轉發。當前實現只是簡單地廣播訂閱消息到所有節點,在某些時候這些將通過過濾或其他算法。