Redis Cluster是Redis的分佈式實現,具備如下目標,按設計重要性排序:node
本文檔中描述的內容在Redis 3.0或更高版本中實現。redis
Redis Cluster實現Redis的非分佈式版本中可用的全部單個鍵命令。執行復雜的多鍵操做(如Set類型聯合或交叉)的命令也能夠實現,只要這些鍵都散列到同一個槽。算法
Redis Cluster實現了一個稱爲hash tags的概念,可用於強制某些key存儲在同一個哈希槽中。可是,在手動從新分片期間,多鍵操做可能會在一段時間內不可用,而單鍵操做始終可用。數據庫
Redis Cluster不支持多個數據庫,例如Redis的獨立版本。只有數據庫0,不容許使用SELECT命令。數組
在Redis集羣中,節點負責保存數據並獲取集羣的狀態,包括將鍵映射到正確的節點。集羣節點還可以自動發現其餘節點,檢測非工做節點,並在須要時促進從節點變成主節點,以便在發生故障時繼續運行。緩存
要執行任務,全部集羣節點都使用TCP總線和二進制協議鏈接,稱爲Redis集羣總線。每一個節點都使用集羣總線鏈接到集羣中的每一個其餘節點。節點使用gossip protocol傳播有關集羣的信息,以便發現新節點,發送ping數據包以確保全部其餘節點正常工做,以及發送發出特定條件信號所需的集羣消息。集羣總線還用於在集羣中傳播發布/訂閱消息,並在用戶請求時協調手動故障轉移(手動故障轉移是故障轉移,不是由Redis集羣故障檢測程序啓動,而是由系統管理員直接啓動)。安全
因爲集羣節點不能代理請求,客戶端可能會被重定向到其餘節點,可使用-MOVED
和-ASK
命令。理論上,客戶端能夠自由地向集羣中的全部節點發送請求,並在須要時重定向,所以客戶端不須要保持集羣的狀態。可是,可以在key和節點之間緩存映射的客戶端能夠以合理的方式提升性能。bash
Redis Cluster使用節點之間的異步複製,上次故障轉移獲勝者擁有隱式合併功能。這意味着最後選出的主數據集最終將替換全部其餘副本。在分區期間總會有一個時間窗口,可能會丟失寫入。然而,在鏈接到大多數主設備的客戶端和鏈接到少數主設備的客戶端的狀況之間,這些窗口是很是不一樣的。服務器
與少數端執行的寫操做相比,Redis Cluster更加努力地保留由鏈接到大多數主服務器的客戶端執行的寫操做。如下是致使在故障期間在多數分區中收到的已確認寫入丟失的狀況示例:網絡
第二種故障模式不太可能發生,由於主節點沒法與大多數其餘主設備通訊足夠的時間進行故障轉移將再也不接受寫入,而且當分區被修復時,寫入仍然會在少許時間內被拒絕容許其餘節點通知配置更改。此故障模式還要求客戶端的路由表還沒有更新。
針對分區的少數端的寫入有一個更大的窗口能夠丟失。例如,Redis Cluster在有少數主設備和至少一個或多個客戶端的分區上丟失了不少的寫入次數,若是多數主設備在故障轉移時發送到主設備的全部寫入可能都會丟失。
具體來講,對於要進行故障轉移的主服務器,至少NODE_TIMEOUT
期間必須由大多數主服務器沒法訪問,所以若是在該時間以前修復了分區,則不會丟失任何寫入。當分區持續時間超過NODE_TIMEOUT
時,在少數端執行的全部寫操做可能會丟失。然而,Redis集羣的少數派一方將在沒有與大多數人接觸的狀況下時間超過NODE_TIMEOUT
時開始拒絕寫入,所以有一個最大窗口,此後少數羣體將再也不可用。所以,在此以後不接受或丟失寫入。
Redis Cluster在分區的少數端不可用。在分區的多數端,假設每一個沒法訪問的主服務器至少有大多數主服務器和從服務器,則服務器會在NODE_TIMEOUT
一段時間後再次可用,再次須要幾秒鐘以便從服務器得到選舉並故障轉移其主服務器,一般在1或2秒內執行)。
這意味着Redis Cluster旨在拯救集羣中幾個節點的故障,但對於須要在大型網絡分裂時須要可用性的應用程序而言,它不是合適的解決方案。
在由N個主節點組成的集羣的示例中,每一個節點具備單個從節點,只要單個節點被分區,集羣的大多數端將保持可用,而且在兩個節點被分區時,將保持可用的機率爲1-(1/(N*2-1))
(在第一個節點失敗後,咱們總共留下了N*2-1
節點,而且惟一沒有副本的主機失敗的機率是1/(N*2-1))
。
例如,在每一個節點具備5個節點和每一個結點都有個slave的集羣中,有1/(5*2-1) = 11.11%
可能性,在兩個節點與多數節點分開後,集羣將再也不可用。
因爲Redis Cluster功能稱爲replicas migration,所以複製副本遷移到孤立主服務器(主服務器再也不具備副本)這一事實能夠改善許多真實場景中的集羣可用性。所以,在每一個成功的故障事件中,集羣能夠從新配置從設備佈局,以便更好地抵抗下一個故障。
在Redis集羣中,節點不會將命令代理到負責給定key的正確節點,而是將客戶端重定向到服務於key空間的給定部分的正確節點。
最終客戶端得到集羣的最新表示以及哪一個節點服務於哪一個key子集,所以在正常操做期間,客戶端直接聯繫正確的節點以發送給定命令。
因爲使用了異步複製,節點不會等待其餘節點的寫入確認(若是未使用WAIT命令顯式請求)。
此外,因爲多鍵命令僅限於近鍵,所以除了從新分片以外,數據永遠不會在節點之間移動。
正常操做的處理方式與單個Redis實例徹底相同。這意味着在具備N個主節點的Redis集羣中,您能夠指望與單個Redis實例相同的性能乘以N,由於設計會線性擴展。同時,查詢一般在單個往返中執行,由於客戶端一般保留與節點的持久鏈接,所以延遲數字也與單個獨立Redis節點狀況相同。
Redis Cluster的主要目標是提供極高的性能和可擴展性,同時保留弱的但合理的數據安全性和可用性。
Redis集羣設計避免了多個節點中相同鍵值對的衝突版本,就像Redis數據模型的狀況同樣,這並不老是使人滿意的。Redis中的值一般很是大; 一般會看到包含數百萬個元素的列表或排序集。數據類型在語義上也很複雜。轉移和合並這些值多是主要瓶頸,and/or可能須要應用程序端邏輯的non-trivial參與,存儲元數據的附加存儲器等等。
這裏沒有嚴格的技術限制。CRDT或同步複製的狀態機能夠模擬相似於Redis的複雜數據類型。可是,此類系統的實際運行時行爲與Redis Cluster不一樣。Redis Cluster的設計旨在涵蓋非集羣Redis版本的確切用例。
key空間分爲16384個槽,有效地設置了16384個主節點的簇大小的上限(但建議的最大節點大小約爲1000個節點)。
集羣中的每一個主節點處理16384個散列槽的子集。當沒有正在進行的集羣從新配置時(即散列插槽從一個節點移動到另外一個節點),集羣是穩定的。當集羣穩定時,單個節點將提供單個散列槽(可是,在網絡分裂或故障的狀況下,服務節點能夠有一個或多個將替換它的從屬,而且能夠用於擴展讀取過期數據的讀取操做)。
用於將鍵映射到散列槽的基本算法以下(讀取此規則的散列標記異常的下一段):
HASH_SLOT = CRC16(key) mod 16384
複製代碼
CRC16規定以下:
使用16個CRC16輸出位中的14個(這就是爲何在上面的公式中存在模16384運算的緣由)。
在咱們的測試中,CRC16在16384個插槽中均勻分配不一樣類型的key時表現很是出色。
注:所用CRC16算法的參考實現可在本文檔的附錄A中找到。
計算用於實現散列標記的散列槽有一個例外。散列標記是一種確保在同一散列槽中分配多個key的方法。這用於在Redis集羣中實現多鍵操做。
爲了實現散列標籤,在某些條件下以稍微不一樣的方式計算key的散列槽。若是key包含一個「{...}」模式僅是{
and}
之間的子串 ,以得到散列slot被散列。可是,因爲可能存在屢次出現{
or}
,如下規則很好地指定了算法:
{
字符。{
右邊有一個字符}
{
和第一次出現}
之間有一個或多個字符。若是知足條件三,不是對key進行散列,而是僅對第一次出現{
和第一次出現}
之間的內容進行散列。
例子:
{user1000}.following
和{user1000}.followers
將散列到相同的散列slot,由於只有在子串user1000
會計算散列slot。foo{}{bar}
,一般將整個鍵進行哈希處理,由於第一次出現{
右側是}
,而中間沒有字符。foo{{bar}}zap
,子串{bar
將被散列,由於它是第一次出現{
和右邊第一次出現}
之間的子串。foo{bar}{zap}
的子串bar
將被散列,算法在第一個有效或無效(無內部字節)匹配{
and}
後中止匹配。{}
,則保證整個散列。當使用二進制數據做爲鍵名時,這頗有用。添加哈希標記異常,如下是Ruby和C語言中HASH_SLOT
函數的實現。
Ruby示例代碼:
def HASH_SLOT(key)
s = key.index "{"
if s
e = key.index "}",s+1
if e && e != s+1
key = key[s+1..e-1]
end
end
crc16(key) % 16384
end
複製代碼
C示例代碼:
unsigned int HASH_SLOT(char *key, int keylen) {
int s, e; /* start-end indexes of { and } */
/* Search the first occurrence of '{'. */
for (s = 0; s < keylen; s++)
if (key[s] == '{') break;
/* No '{' ? Hash the whole key. This is the base case. */
if (s == keylen) return crc16(key,keylen) & 16383;
/* '{' found? Check if we have the corresponding '}'. */
for (e = s+1; e < keylen; e++)
if (key[e] == '}') break;
/* No '}' or nothing between {} ? Hash the whole key. */
if (e == keylen || e == s+1) return crc16(key,keylen) & 16383;
/* If we are here there is both a { and a } on its right. Hash
* what is in the middle between { and }. */
return crc16(key+s+1,e-s-1) & 16383;
}
複製代碼
每一個節點在集羣中都有惟一的名稱。節點名稱是160位隨機數的十六進制表示,在第一次啓動節點時得到(一般使用/dev/urandom)。節點將其ID保存在節點配置文件中,並將永久使用相同的ID,或者至少只要系統管理員未刪除節點配置文件,或經過CLUSTER RESET命令請求硬重置。
節點ID用於標識整個集羣中的每一個節點。給定節點能夠更改其IP地址,而無需也更改節點ID。集羣還可以檢測IP /端口的變化,並使用在集羣總線上運行的gossip protocol進行從新配置。
節點ID不是與每一個節點關聯的惟一信息,而是惟一始終全局一致的信息。每一個節點還具備如下相關信息集。某些信息是關於此特定節點的集羣配置詳細信息,而且最終在集羣中保持一致。其餘一些信息,例如上次節點被ping時,對每一個節點來講都是本地的。
每一個節點都維護有關集羣中知道的其餘節點的如下信息:節點ID,節點的IP和端口,一組標誌,標記爲節點的主節點slave
,上次節點被ping後的時間戳,最近接收到pong的節點的時間戳, configuration epoch(在本說明書後面解釋),鏈路狀態以及最後服務的散列slots集合。
CLUSTER NODES文檔中描述了全部節點字段的詳細說明。
集羣節點命令可在簇中被髮送到任何節點,並提供該集羣的狀態,並根據本地視圖所查詢的節點具備集羣的每一個節點的信息。
如下是發送到三個節點的小型集羣中的主節點的CLUSTER NODES命令的示例輸出。
$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 127.0.0.1:6379 myself - 0 1318428930 1 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 2 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 3 connected 2730-4095
複製代碼
在上面的列表中,不一樣的字段按順序排列:節點id,地址:端口,標誌,最後ping發送,最後接收到的pong,configuration epoch,鏈路狀態,slots。一旦咱們談到Redis Cluster的特定部分,咱們將詳細介紹上述領域的詳細信息。
每一個Redis集羣節點都有一個額外的TCP端口,用於接收來自其餘Redis集羣節點的傳入鏈接。此端口與用於接收來自客戶端的傳入鏈接的普通TCP端口相距固定偏移量。要獲取Redis集羣端口,應將10000添加到普通命令端口。例如,若是Redis節點正在偵聽端口6379上的客戶端鏈接,則還將打開集羣總線端口16379。
節點到節點的通訊僅使用集羣總線和集羣總線協議進行:由不一樣類型和大小的幀組成的二進制協議。集羣總線二進制協議未公開記錄,由於它不是用於外部軟件設備使用此協議與Redis集羣節點通訊。可是,您能夠經過讀取Redis集羣源代碼中的cluster.h
和cluster.c
文件來獲取有關集羣總線協議的更多詳細信息 。
Redis Cluster是一個完整的網格,其中每一個節點使用TCP鏈接與每一個其餘節點鏈接。
在N個節點的集羣中,每一個節點具備N-1個傳出TCP鏈接和N-1個傳入鏈接。
這些TCP鏈接始終保持活動狀態,不按需建立。當節點指望在響應集羣總線中的ping時發出pong應答時,在等待足夠長的時間以將節點標記爲不可達以前,它將嘗試經過從頭開始從新鏈接來刷新與節點的鏈接。
當Redis Cluster節點造成一個完整的網格時,節點使用gossip protocol和配置更新機制,以免在正常狀況下在節點之間交換太多消息,所以交換的消息數量不是指數級的。
節點始終接受集羣總線端口上的鏈接,甚至在收到ping時也會回覆ping,即便ping節點不受信任也是如此。可是,若是發送節點不被視爲集羣的一部分,則接收節點將丟棄全部其餘分組。
節點將僅以兩種方式接受另外一個節點做爲集羣的一部分:
若是節點爲本身顯示MEET
消息。MEET
消息與PING消息幾乎徹底相同,但強制接收者接受節點做爲集羣的一部分。僅當系統管理員經過如下命令請求時,節點纔會將MEET
消息發送到其餘節點:
CLUSTER MEET ip port
若是已經信任的節點談及另外一個節點,則節點還將另外一個節點註冊爲集羣的一部分。所以,若是A知道B,而且B知道C,則最終B將向A發送關於C的gossip消息。當發生這種狀況時,A將註冊C做爲網絡的一部分,並將嘗試與C鏈接。
這意味着只要咱們鏈接任何鏈接圖中的節點,它們最終將自動造成徹底鏈接的圖。這意味着集羣可以自動發現其餘節點,但前提是系統管理員強制創建了信任關係。
此機制使集羣更加健壯,但可防止不一樣的Redis集羣在更改IP地址或其餘網絡相關事件後意外混合。
Redis客戶端能夠自由地向集羣中的每一個節點發送查詢,包括從節點。節點將分析查詢,若是它是可接受的(即,查詢中只提到一個key,或者提到的多個key都是相同的哈希槽),它將查找哪一個節點負責哈希槽key或key所屬的地方。
若是節點爲哈希槽提供服務,則只處理查詢,不然節點將檢查其內部哈希槽到節點映射,並將回覆具備MOVED錯誤的客戶端,以下例所示:
GET x
-MOVED 3999 127.0.0.1:6381
複製代碼
該錯誤包括key的哈希槽(3999)和能夠爲查詢提供服務的實例的ip:端口。客戶端須要將查詢從新發出到指定節點的IP地址和端口。請注意,即便客戶端在從新發出查詢以前等待很長時間,而且同時集羣配置發生更改,若是散列槽3999如今由另外一個節點提供服務,則目標節點將再次回覆MOVED錯誤。若是聯繫的節點沒有更新的信息,則會發生相同的狀況
所以,從集羣節點的角度來看,咱們嘗試經過ID來簡化咱們與客戶端的接口,只是在哈希槽和由IP:端口對識別的Redis節點之間公開映射。
客戶端不是必需的,但應該嘗試記住127.0.0.1:6381提供的哈希槽3999。這樣,一旦須要發出新命令,它就能夠計算目標key的散列槽而且更有可能選擇正確的節點。
另外一種方法是在收到MOVED重定向時使用CLUSTER NODES或CLUSTER SLOTS命令刷新整個客戶端集羣佈局。遇到重定向時,可能會從新配置多個插槽而不是一個,所以儘快更新客戶端配置一般是最佳策略。
請注意,當集羣穩定(配置中沒有持續更改)時,最終全部客戶端都將得到散列插槽映射 - >節點,從而使集羣高效,客戶端直接尋址正確的節點而無需重定向,代理或其餘單個節點失敗點實體。
客戶端還必須可以處理本文檔後面描述的**-ASK重定向**,不然它不是完整的Redis集羣客戶端。
Redis Cluster支持在集羣運行時添加和刪除節點的功能。添加或刪除節點被抽象爲相同的操做:將哈希槽從一個節點移動到另外一個節點。這意味着可使用相同的基本機制來從新平衡集羣,添加或刪除節點等。
實現的核心是移動哈希槽的能力。從實際的角度來看,哈希槽只是一組key,所以Redis Cluster在從新分片期間的確實作的是將key從一個實例移動到另外一個實例。移動哈希槽意味着將哈希的合適的全部key移動到此哈希槽中。
要了解其工做原理,咱們須要顯示CLUSTER
用於操做Redis集羣節點中的插槽轉換表的子命令。
可使用如下子命令(在這種狀況下,其餘子命令無用):
前兩個命令ADDSLOTS
和DELSLOTS
,僅用於將插槽分配(或刪除)到Redis節點。分配時隙意味着告訴給定主節點它將負責存儲和提供指定散列槽的內容。
在分配散列槽以後,它們將使用gossip協議在集羣中傳播,如稍後在配置傳播部分中所指定的 。
ADDSLOTS
當從頭開始建立新集羣時,一般會使用該命令,以便爲每一個主節點分配全部可用的16384個散列插槽的子集。
將DELSLOTS
主要用於集羣配置的人工修改或用於調試任務:在實踐中不多使用。
SETSLOT
若是使用SETSLOT <slot> NODE
表單,子命令用於將槽分配給特定節點ID 。不然,插槽能夠在兩種特殊狀態進行設置MIGRATING
和IMPORTING
。使用這兩個特殊狀態是爲了將散列槽從一個節點遷移到另外一個節點。
-ASK
重定向將查詢轉發到做爲遷移目標的節點。ASKING
命令。若是ASKING
客戶端未給出該命令,則查詢將經過重定向錯誤-MOVED
,重定向到真正的哈希槽全部者。讓咱們經過哈希槽遷移的例子來講明這一點。假設咱們有兩個Redis主節點,稱爲A和B。咱們想將散列槽8從A移動到B,因此咱們發出以下命令:
CLUSTER SETSLOT 8 IMPORTING A
。CLUSTER SETSLOT 8 MIGRATING B
。每次使用屬於散列槽8的key查詢客戶端時,全部其餘節點將繼續將客戶端指向節點「A」,所以會發生如下狀況:
這樣咱們就再也不在「A」中建立新key了。與此同時,redis-trib
在從新分片和Redis集羣配置期間使用的特殊程序將把散列槽8中的現有key從A遷移到B.這是使用如下命令執行的:
CLUSTER GETKEYSINSLOT slot count
複製代碼
上面的命令將返回count
指定哈希槽中的鍵。對於返回的每一個key,redis-trib
向節點「A」發送一個MIGRATE命令,該命令將以原子方式將指定的key從A遷移到B(兩個實例都被鎖定了遷移key所需的時間(一般是很是小的時間),所以存在沒有競爭條件)。這就是MIGRATE的工做原理:
MIGRATE target_host target_port key target_database id timeout
複製代碼
MIGRATE將鏈接到目標實例,發送key的序列化版本,一旦收到OK代碼,將刪除其本身的數據集中的舊key。從外部客戶端的角度來看,key在任何給定時間存在於A或B中。
在Redis集羣中,不須要指定0之外的數據庫,但 MIGRATE是一個通用命令,可用於不涉及Redis集羣的其餘任務。 即便在移動複雜key(如長列表)時,MIGRATE也會盡量快地進行優化,但在Redis集羣中,若是使用數據庫的應用程序存在延遲限制,則從新配置存在bigkey的集羣不被視爲明智的過程。
當遷移過程最終完成時,該SETSLOT <slot> NODE <node-id>
命令被髮送到遷移中涉及的兩個節點,以便再次將槽設置爲其正常狀態。一般會將相同的命令發送到全部其餘節點,以免等待新配置在集羣中的天然傳播。
在上一節中,咱們簡要介紹了ASK重定向。爲何咱們不能簡單地使用MOVED重定向?由於雖然MOVED意味着咱們認爲哈希槽是由不一樣節點永久服務的,而且應該針對指定節點嘗試下一個查詢,但ASK意味着僅將下一個查詢發送到指定節點。
這是必需的,由於關於散列槽8的下一個查詢能夠是關於仍在A中的key,所以咱們老是但願客戶端嘗試A,而後在須要時嘗試B。因爲這僅發生在16384可用的一個散列槽中,所以集羣上的性能能夠接受。
咱們須要強制該客戶端行爲,所以爲了確保客戶端在A嘗試以後只嘗試節點B,若是客戶端在發送查詢以前發送ASKING命令,則節點B將僅接受設置爲IMPORTING的插槽的查詢。
基本上,ASKING命令在客戶端上設置一次性標誌,強制節點提供有關IMPORTING槽的查詢。
從客戶端的角度來看,ASK重定向的完整語義以下:
一旦散列槽8遷移完成,A將發送MOVED消息,而且客戶端能夠將散列槽8永久映射到新的IP和端口對。請注意,若是有錯誤的客戶端先前執行了映射,這不是問題,由於它在發出查詢以前不會發送ASKING命令,所以B將使用MOVED重定向錯誤將客戶端重定向到A.
插槽遷移以相似的術語解釋,但在CLUSTER SETSLOT 命令文檔中使用不一樣的措辭(爲了文檔中的冗餘)。
雖然有可能讓Redis集羣客戶端實現不記得內存中的插槽配置(插槽號和節點的地址之間的映射),而且只能經過聯繫等待重定向的隨機節點來工做,這樣的客戶端將是很是低效的。
Redis集羣客戶端應該嘗試足夠智能以記住插槽配置。可是,此配置不須要是最新的。因爲聯繫錯誤的節點只會致使重定向,所以應該觸發客戶端視圖的更新。
客戶端一般須要在兩種不一樣的狀況下獲取完整的插槽列表和映射的節點地址:
MOVED
接收到重定向。請注意,客戶端能夠MOVED
經過僅更新其表中移動的插槽來處理重定向,但這一般效率不高,由於一般會當即修改多個插槽的配置(例如,若是將從屬設備提高爲主服務器,則全部服務舊master的插槽將從新映射)。MOVED
經過從頭開始向節點提取完整的插槽映射來對重定向作出反應要簡單得多。
爲了檢索插槽配置,Redis Cluster提供了不須要解析的CLUSTER NODES命令的替代方法,而且僅提供客戶端嚴格須要的信息。
新命令稱爲CLUSTER SLOTS,它提供一個插槽範圍數組,以及服務於指定範圍的關聯主節點和從屬節點。
如下是CLUSTER SLOTS輸出的示例:
127.0.0.1:7000> cluster slots
1) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 7001
4) 1) "127.0.0.1"
2) (integer) 7004
2) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
4) 1) "127.0.0.1"
2) (integer) 7003
3) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
4) 1) "127.0.0.1"
2) (integer) 7005
複製代碼
返回數組的每一個元素的前兩個子元素是範圍的起始端插槽。附加元素表示地址 - 端口對。第一個地址 - 端口對是服務於插槽的主設備,附加的地址端口對是服務於相同插槽的全部從設備,它們不處於錯誤狀態(即未設置FAIL標誌)。
例如,輸出的第一個元素表示從5461到10922(包括起始和結束)的插槽由127.0.0.1:7001提供服務,而且能夠看到只讀slave的信息:127.0.0.1:7004。
若是集羣配置錯誤,則沒法保證CLUSTER SLOTS返回覆蓋完整16384個插槽的範圍,所以客戶端應使用NULL對象初始化填充目標節點的插槽配置映射,並在用戶嘗試執行有關key的命令時報告錯誤屬於未分配的插槽。
在發現未分配插槽時將錯誤返回給調用方以前,客戶端應嘗試再次獲取插槽配置以檢查集羣是否已正確配置。
使用哈希標記,客戶端能夠自由使用多鍵操做。例如,如下操做有效:
MSET {user:1000}.name Angela {user:1000}.surname White
複製代碼
當key所屬的散列槽的從新分片正在進行時,多鍵操做可能變得不可用。
更具體地說,即便在從新分片期間,仍然能夠得到全部存在且仍然散列到相同slot(源節點或目的地節點)的多鍵操做。
對於不存在或在從新分片期間在源節點和目標節點之間拆分的鍵的操做將生成-TRYAGAIN
錯誤。客戶端能夠在一段時間後嘗試操做,或報告錯誤。
一旦指定的散列槽的遷移終止,全部多鍵操做再次可用於該散列槽。
一般,從節點會將客戶端重定向到給定命令中涉及的散列槽的權威主節點,可是客戶端可使用從屬節點來使用READONLY命令擴展讀取。
READONLY告訴Redis集羣從屬節點客戶端能夠讀取可能過期的數據,而且對運行寫入查詢不感興趣。
當鏈接處於只讀模式時,僅當操做涉及未由從屬主節點提供的key時,集羣纔會向客戶端發送重定向。這多是由於:
發生這種狀況時,客戶端應更新其散列圖映射,如前面部分所述。
可使用READWRITE命令清除鏈接的只讀狀態。
Redis集羣節點不斷交換ping和pong數據包。這兩種數據包具備相同的結構,而且都攜帶重要的配置信息。惟一的實際區別是消息類型字段。咱們將ping和pong包的總和稱爲心跳包。
一般節點發送ping數據包,觸發接收器回覆pong數據包。然而,這不必定是真的。節點能夠僅發送pong數據包以向其餘節點發送有關其配置的信息,而不會觸發回覆。例如,這是有用的,以便儘快廣播新配置。
一般,節點將每秒ping幾個隨機節點,以便每一個節點發送的ping數據包總數(以及接收到的pong數據包)是一個恆定的數量,而無論集羣中的節點數量。
可是,每一個節點都會確保ping全部其餘節點不會讓發送ping或接收pong的節點的時間超過一半的NODE_TIMEOUT
。在NODE_TIMEOUT
通過以前,節點還嘗試將TCP鏈路與另外一個節點從新鏈接,以確保不會僅由於當前TCP鏈接存在問題而不相信節點不可達。
若是NODE_TIMEOUT
設置爲一個小數字而且節點數(N)很是大,則全局交換的消息數量能夠是至關大的,由於每一個節點將嘗試每隔一半NODE_TIMEOUT
時間ping它們沒有獲取到新信息的每一個其餘節點。
例如,在節點超時設置爲60秒的100節點集羣中,每一個節點將嘗試每30秒發送99個ping,總ping數爲3.3 /秒。乘以100個節點,在整個集羣中每秒330次ping。
有一些方法能夠下降消息數量,但Redis Cluster故障檢測當前使用的帶寬沒有報告問題,所以目前使用了明顯且直接的設計。注意,即便在上面的例子中,每秒交換的330個數據包在100個不一樣的節點之間均勻分配,所以每一個節點接收的流量是可接受的。
Ping和pong數據包包含全部類型數據包通用的標頭(例如,請求故障轉移投票的數據包),以及特定於Ping和Pong數據包的特殊Gossip部分。
公共標頭具備如下信息:
currentEpoch
和configEpoch
字段,用於掛載Redis Cluster使用的分佈式算法(這將在下一節中詳細介紹)。若是節點是從屬節點,則它configEpoch
是configEpoch
其主節點的最後一個節點。Ping和pong包也包含gossip部分。本節向接收方提供發送方節點對集羣中其餘節點的見解。gossip部分僅包含關於發送者已知的節點集中的幾個隨機節點的信息。gossip部分中提到的節點數量與集羣大小成比例。
對於在gossip部分中添加的每一個節點,將報告如下字段:
gossip部分容許接收節點從發送者的角度得到關於其餘節點的狀態的信息。這對於故障檢測和發現集羣中的其餘節點都頗有用。
Redis集羣故障檢測用於識別大多數節點沒法再訪問主節點或從節點,而後經過將從屬設備提高爲主節點來進行響應。當沒法進行從屬提高時,集羣將處於錯誤狀態以中止接收來自客戶端的查詢。
如前所述,每一個節點都採用與其餘已知節點相關聯的標誌列表。有兩個標誌用於故障檢測,被稱爲PFAIL
和FAIL
。PFAIL
表示可能的故障,而且是未確認的故障類型。FAIL
意味着節點出現故障,而且大多數master在固定的時間內確認了這一狀況。
PFAIL標誌:
PFAIL
當節點不可訪問超過NODE_TIMEOUT
時間時,節點使用該標誌標記另外一個節點。主節點和從節點均可以標記另外一個節點PFAIL
,不管其類型如何。
Redis集羣節點的不可達性概念是咱們有一個活動的ping(咱們發送的ping,咱們尚未獲得回覆)等待的時間超過NODE_TIMEOUT
。對於這種工做機制,NODE_TIMEOUT
與網絡往返時間相比必須很大。爲了在正常操做期間增長可靠性,節點將嘗試在NODE_TIMEOUT
已通過去一半的狀況下與集羣中的其餘節點從新鏈接,而不會回覆ping。此機制可確保鏈接保持活動狀態,所以斷開的鏈接一般不會形成節點之間錯誤的故障報告。
Fail標誌:
PFAIL
標誌就是本地信息對其餘節點信息的見解,但它不足以觸發一個slave節點稱爲主節點。對於要被視爲關閉的節點,須要將PFAIL
條件升級到FAIL
條件。
如本文檔的節點心跳部分所述,每一個節點都向每一個其餘節點發送gossip消息,包括一些隨機已知節點的狀態。每一個節點最終都會爲每一個其餘節點接收一組節點標誌。這樣,每一個節點都有一個機制來向其餘節點發出有關它們檢測到的故障狀況的信號
假如PFAIL
條件升級爲FAIL
條件時,下面的一組條件將知足:
PFAIL
。NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT
(在當前實現中,有效性因子設置爲2,所以這只是時間NODE_TIMEOUT
的兩倍)時間內發出信號PFAIL
或FAIL
狀態。若是知足以上全部條件,則節點A將:
FAIL
。FAIL
向全部可訪問節點發送消息。該FAIL
消息將強制每一個接收節點將此節點狀態標記爲Fail
,不管它是否已經標記了PFAIL
狀態中的節點。
請注意,FAIL標誌主要是單向的。也就是說,一個節點能夠去從PFAIL
到FAIL
,但FAIL
標誌只能在下列狀況下被清除:
FAIL
能夠清除標誌,由於slave未進行故障轉移。FAIL
能夠清除標誌,由於沒有插槽的主服務器不會真正參與集羣,而且正在等待配置以加入集羣。NODE_TIMEOUT
)已通過去而沒有任何可檢測的從屬促銷。它最好從新加入集羣並在這種狀況下繼續。值得注意的是,雖然PFAIL
- > FAIL
過渡使用了一種協議形式,但使用的協議很弱:
FAIL
條件的每一個節點都將使用該FAIL
消息強制該集羣中的其餘節點上的該條件,可是沒法確保該消息將到達全部節點。例如,節點能夠檢測到該FAIL
條件,而且因爲分區將沒法到達任何其餘節點。可是,Redis集羣故障檢測具備活躍度要求:最終全部節點都應該就給定節點的狀態達成一致。有兩種狀況可能源於裂腦狀況。一些少數節點認爲節點處於FAIL
狀態,或者少數節點認爲節點不處於FAIL
狀態。在這兩種狀況下,最終集羣將具備給定節點狀態的單個視圖:
狀況1:若是大多數主機已將節點標記爲FAIL
因爲故障檢測及其產生的鏈效應,則每一個其餘節點最終將標記主機FAIL
,由於在指定的時間窗口中將報告足夠的故障。
狀況2:當只有少數master標記了一個節點時FAIL
,slave升級爲master將不會發生(由於它使用更正式的算法,確保每一個人最終都知道slave升級),而且每一個節點將根據FAIL
狀態清除FAIL
狀態清除上述規則(即通過N次NODE_TIMEOUT
後沒有變化)。
該FAIL標誌僅用做觸發器來運行slave變成master的算法的安所有分。理論上,從屬設備能夠在其主設備沒法訪問時獨立啓動slave promotion,並等待主設備拒絕提供確認(若是主設備實際上可由多數人訪問)。然而,因爲PFAIL -> FAIL
狀態複雜性的增長,薄弱的協議,以及FAIL
強制在集羣的可到達部分中以最短的時間傳播狀態的消息具備實際優勢。因爲這些機制,若是集羣處於錯誤狀態,一般全部節點將幾乎同時中止接受寫入。從使用Redis Cluster的應用程序的角度來看,這是一個理想的功能。還避免了因爲本地問題而沒法到達其主設備的從設備發起的錯誤選舉嘗試(主設備可由大多數其餘主節點到達)。
Redis Cluster使用相似於Raft算法「term」的概念。在Redis Cluster中,該術語稱爲epoch,它用於爲事件提供增量版本控制。當多個節點提供衝突信息時,另外一個節點能夠了解哪一個狀態是最新的。
這currentEpoch
是一個64位無符號數。
在建立節點時,每一個Redis Cluster節點(從屬節點和主節點)都將其currentEpoch
設置爲0。
每當從另外一節點接收到分組時,若是發送方的epoch(集羣總線消息報頭的一部分)大於本地節點epoch,currentEpoch
則更新爲發送方時期。
因爲這些語義,最終全部節點都將採用集羣中的最好節點的configEpoch
。
當集羣的狀態發生變化且節點尋求協議以執行某些操做時,將使用此信息。
目前,這隻發生在slave promotion期間,以下一節所述。基本上,epoch是集羣的邏輯時鐘,而且要求給定的信息將具備較小epoch的集羣統一。
每一個master老是在ping和pong數據包中公佈configEpoch
,以及一個位圖公佈其服務的插槽集。
建立新節點時,在master中將configEpoch
設置爲零。
在slave選舉期間建立了一個新的configEpoch
。slave試圖增長了它們的epoch來取代失敗的master,並試圖得到大多數master的受權。當slave被受權時,將建立一個新的惟一的configEpoch
,而且slave變成master後將使用新的configEpoch
。
以下一節所述,當不一樣節點聲明不一樣的配置(因爲網絡分區和節點故障而可能發生的狀況)時,configEpoch
有助於解決衝突。
從節點還在ping和pong數據包中通告該configEpoch
字段,可是在slave的狀況下,該configEpoch
字段表示它們最後一次交換數據包時的主節點的配置。這容許其餘實例檢測從屬設備什麼時候具備須要更新的舊配置(主節點不會授予具備舊配置的slave的投票權限)。
每次更改某個已知節點的configEpoch
時,它都會被接收到此信息的全部節點永久存儲在nodes.conf文件中。currentEpoch
值也會發生一樣的狀況。保證fsync-ed
在節點繼續運行以前更新這兩個變量並保存到磁盤。
在故障轉移期間使用簡單算法生成的configEpoch
值保證是新的,增量的和惟一的。
slave節點選舉和晉升由slave節點處理,在主節點的投票支持下實現slave promotion。當主機FAIL
處於從至少一個具備先決條件以成爲主設備的從設備的角度處於狀態時,發生從設備選舉。
爲了讓slave可以自我提高,它須要開始選舉並贏得選舉。若是master處於FAIL
狀態,那麼給定master的全部slave均可以開始選舉,可是隻有一個slave會贏得選舉並促使本身掌握節點。
當知足如下條件時,slave開始選舉:
FAIL
狀態。爲了被選舉,slave的第一步是增長其currentEpoch
計數器,並從主實例請求投票。
經過將FAILOVER_AUTH_REQUEST
分組廣播到集羣的每一個主節點,slave請求投票。而後它等待兩倍的回覆最大時間NODE_TIMEOUT
到達(但老是至少2秒)。
一旦master投票給一個給定的slave,回答FAILOVER_AUTH_ACK
,NODE_TIMEOUT * 2
段時間內它就不能再投票給同一個master的另外一個slave。在此期間,它將沒法回覆同一主機的其餘受權請求。這不是保證安全所必需的,但對於防止多個slaveconfigEpoch
在大約同一時間(一般是不一樣的)被選中(一般不須要)是有用的。
從屬服務器在收到epoch時間小於發送投票請求時的currentEpoch
時丟棄任何AUTH_ACK
回覆。這確保它不計算用於先前選舉的投票。
一旦slave接收到來自大多數master的ACK,它就贏得了選舉。不然,若是在兩倍NODE_TIMEOUT
(但老是至少2秒)的時間內未得到多數投票,則選舉停止,而且在NODE_TIMEOUT * 4
(而且老是至少4秒)以後將再次嘗試新的選舉。
一旦master處於FAIL
狀態,slave會在嘗試晉升以前等待一小段時間。該延遲計算以下:
DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds +
SLAVE_RANK * 1000 milliseconds.
複製代碼
固定延遲確保咱們等待FAIL
狀態在集羣中傳播,不然slave可能會在其餘masters仍然不知道此masterFAIL
狀態時嘗試當選,拒絕投票給它們
隨機延遲用於使slaves去同步,所以它們不太可能同時開始選舉。
這SLAVE_RANK
是slave關於它從master處理的複製數據量的級別。當主設備發生故障時,從設備交換消息以創建(best effort)排名:具備最新複製偏移的從設備爲等級0,第二高的等級爲1,依此類推。經過這種方式,最新的slave試圖在其餘人以前當選。
排名順序沒有嚴格執行; 若是更高級別的slave未能當選,其餘人將很快嘗試。
一旦slave贏得選舉,它將得到一個新的惟一和增量的configEpoch
,高於任何其餘現有master。它開始在ping和pong數據包中宣傳本身做爲master的角色,提供一組服務的插槽與configEpoch
。
爲了加速其餘節點的從新配置,將pong分組廣播到集羣的全部節點。目前沒法訪問的節點在從另外一個節點接收到ping或pong數據包時,最終將被從新配置,或者若是檢測到它經過心跳包發佈的信息已過時,則將從另外一個節點接收UPDATE
數據包。
其餘節點將檢測到有一個新主服務器爲舊主服務器提供服務但具備更好的configEpoch
以及具備相同插槽服務,並將升級其配置。舊主服務器的從服務器(若是它從新加入集羣,則是故障轉移主服務器)不只會升級配置,還會從新配置以重新主服務器進行復制。如何配置從新加入集羣的節點將在下一節中介紹。
在上一節中,討論了slave如何試圖當選。本節解釋了從請求爲給定從屬者投票的master的角度發生的事情。
master們以FAILOVER_AUTH_REQUEST
要求形式收到slave的投票請求。
要得到投票,須要知足如下條件:
currentEpoch
值不大於lastVoteEpoch,它就會拒絕再次投票。當master對投票請求做出確定回覆時,lastVoteEpoch會相應更新,並安全地存儲在磁盤上。FAIL
時,masters纔會投票給slave。currentEpoch
小於master的currentEpoch
將被忽略。所以,master回覆的currentEpoch
將始終與auth請求相同。若是同一個slave再次要求投票,增長currentEpoch
,能夠保證不能接受來自master的舊延遲迴複用於新投票。不使用規則3致使的問題示例:
mastercurrentEpoch
是5,lastVoteEpoch是1(這可能發生在選舉失敗後)
currentEpoch
是3。currentEpoch
5 回答肯定,但回覆延遲了。currentEpoch
5,並被接受爲有效。NODE_TIMEOUT * 2
過去以前投票給同一master的slave。這不是嚴格要求的,由於兩個slave不可能在同一時期贏得選舉。可是,實際上它確保當一個slave被選中時,它有足夠的時間通知其餘slave,並避免另外一個slave贏得新選舉的可能性,執行沒必要要的第二次故障轉移。FAIL
,而且master沒有在當前任期內投票,則給予正面投票。最好的slave是最有可能開始選舉並在其餘slave選舉以前贏得它,由於它一般可以提早開始投票過程,由於它的*排名更高,*如上一節所述。configEpoch
數量小於master表中的任何一個configEpoch
。請記住,從屬設備發送其主設備configEpoch
,以及主設備提供的插槽位圖。這意味着請求投票的slave必須具備其想要故障轉移的插槽的配置,該配置新於或等於授予投票的主設備。本節說明了如何使用epoch概念使slave promotion過程對分區更具抵抗力。
此時B已關閉且A再次具備master的角色(實際上UPDATE
消息會當即從新配置它,但在這裏咱們假設全部UPDATE
消息都丟失了)。與此同時,slaveC將嘗試當選,以便將B故障轉移。這就是:
configEpoch
。正如您將在下一節中看到的,從新加入集羣的陳舊節點一般會盡快收到有關配置更改的通知,由於只要它ping任何其餘節點,接收方就會檢測到它有陳舊信息並將發送一個UPDATE
信息。
Redis Cluster的一個重要部分是用於傳播有關哪一個集羣節點爲一組給定哈希槽服務的信息的機制。這對於新集羣的啓動和在從屬服務器提高爲其故障主服務器的插槽提供服務後升級配置的能力相當重要。
相同的機制容許以無限長的時間劃分的節點以合理的方式從新加入集羣。
哈希槽配置有兩種傳播方式:
UPDATE
消息。因爲在每一個心跳包中都有關於所服務的發送方configEpoch
和一組哈希slot的信息,若是心跳包的接收方發現發送方信息是陳舊的,它將發送包含新信息的包,迫使過期節點更新其信息。心跳或UPDATE
消息的接收器使用某些簡單規則以便將其表映射散列槽更新到節點。建立新的Redis集羣節點時,其本地哈希槽表將簡單地初始化爲NULL
條目,以便每一個哈希槽不綁定或連接到任何節點。這看起來相似於如下內容:
0 -> NULL
1 -> NULL
2 -> NULL
...
16383 -> NULL
複製代碼
爲了更新其哈希槽表,第一個遵循節點的規則以下:
規則1:若是散列槽未分配(設置爲NULL
),而且已知節點聲明它,我將修改個人散列槽表並將聲明的散列槽與其關聯。
所以,若是咱們從節點A接收到聲稱爲configEpoch值爲3的散列插槽1和2提供服務的心跳,則該表將被修改成:
0 -> NULL
1 -> A [3]
2 -> A [3]
...
16383 -> NULL
複製代碼
建立新集羣時,系統管理員須要手動分配(使用CLUSTER ADDSLOTS命令,經過redis-trib命令行工具或經過任何其餘方式)每一個主節點服務的插槽僅用於節點自己,以及信息將快速傳播到集羣中。
可是這條規則還不夠。咱們知道散列槽映射能夠在兩個事件期間發生變化:
如今讓咱們關注故障轉移。當從設備故障轉移其主設備時,它得到configEpoch,該configEpoch保證大於其主設備之一(而且一般大於先前生成的任何其餘配置時期)。例如,做爲A的從屬節點B,可使用configEpoch 4來故障轉移B。它將開始發送心跳包(第一次在集羣範圍內進行大規模廣播),而且因爲如下第二規則,接收器將更新他們的哈希槽表:
規則2:若是已經分配了一個散列槽,而且一個已知節點正在使用configEpoch
大於當前與該槽相關聯的主節點的configEpoch
通告它,我將把散列槽從新綁定到新節點。
所以,在接收到來自B的消息聲稱服務於配置時期爲4的散列插槽1和2以後,接收器將按如下方式更新其表:
0 -> NULL
1 -> B [4]
2 -> B [4]
...
16383 -> NULL
複製代碼
活動屬性:因爲第二個規則,最終集羣中的全部節點都會贊成插槽的全部者是公佈它的節點中具備最大configEpoch
的插槽的全部者。
Redis集羣中的此機制稱爲last failover wins。
在從新分析期間也會發生一樣的狀況。當導入散列槽的節點完成導入操做時,其configEpoch
會增長,以確保更改將在整個集羣中傳播。
考慮到上一節,更容易瞭解更新消息的工做原理。節點A可能在一段時間後從新加入集羣。它將發送心跳包,聲稱它服務於散列槽1和2,配置時期爲3.全部具備更新信息的接收器將看到相同的散列槽與具備更高配置時期的節點B相關聯。所以,他們將使用插槽的新配置向A 發送UPDATE
消息。因爲上面的規則2,A將更新其配置 。
當節點從新加入集羣時,將使用相同的基本機制。繼續上面的例子,節點A將被通知哈希槽1和2如今由B服務。假設這兩個是A服務的惟一哈希槽,A服務的哈希槽的數量將降低到0!因此A將從新配置成新master的slave。
遵循的實際規則比這複雜一點。一般,可能會發生A在不少時間以後從新加入,同時可能發生最初由A服務的哈希時隙由多個節點服務,例如,哈希slot 1能夠由B服務,而哈希時隙2由C提供。
所以,實際的Redis集羣節點角色切換規則是:主節點將更改其配置以複製(做爲其從屬)其最後一個散列槽的節點。
在從新配置期間,最終服務的散列槽的數量將降低到零,而且節點將相應地從新配置。請注意,在基本狀況下,這隻意味着舊主服務器將成爲在故障轉移後替換它的從服務器的從服務器。可是,在通常形式中,規則涵蓋全部可能的狀況。
從屬設備徹底相同:它們從新配置以複製其前主設備的最後一個哈希槽的節點。
Redis Cluster實現了一個名爲副本遷移的概念,以提升系統的可用性。咱們的想法是,在具備主從設置的集羣中,若是從設備和主設備之間的映射是固定的,則若是發生單個節點的多個獨立故障,則可用性隨時間受到限制。
例如,在每一個主服務器都有一個從服務器的集羣中,只要主服務器或從服務器發生故障,集羣就能夠繼續運行,但若是二者都失敗,則集羣能夠繼續運行。然而,存在一類故障,這些故障是因爲可能隨時間累積的硬件或軟件問題引發的單個節點的獨立故障。例如:
若是主服務器和從服務器之間的映射是固定的,那麼使集羣更能抵抗上述狀況的惟一方法是向每一個主服務器添加從服務器,但這樣作成本很高,由於它須要執行更多Redis實例,更多內存和等等。
另外一種方法是在集羣中建立不對稱,讓集羣佈局隨着時間的推移自動更改。例如,集羣能夠具備三個主設備A,B,C。A和B各自具備單個從設備A1和B1。然而,主設備C是不一樣的而且具備兩個從設備:C1和C2。
副本遷移是自動從新配置從站以便遷移到再也不覆蓋的主站(無工做從站)的過程。使用副本遷移,上面提到的場景變爲:
遷移算法不使用任何形式的協議,由於Redis集羣中的從屬佈局不是須要與configEpoch一致和/或版本化的集羣配置的一部分。相反,當沒有支持主服務器時,它使用算法來避免從服務器的大規模遷移。該算法最終確保(一旦集羣配置穩定),每一個主設備將由至少一個從設備支持。
這就是算法的工做原理。首先,咱們須要在此上下文中定義什麼是 好的從屬:從給定節點的角度來看,良好的從屬是沒有FAIL
從屬狀態的從屬。
在每一個從設備中觸發算法的執行,該從設備檢測到至少有一個沒有良好從設備的主設備。然而,在檢測到這種狀況的全部slave中,只有一個子集應該起做用。該子集實際上一般是單個從設備,除非不一樣的從設備在給定時刻具備其餘節點的故障狀態的略微不一樣的視圖。
活躍slave是具備最大可達master鏈接的從站數,不是在FAIL狀態,並具備最小的ID節點的slave。
所以,例如,若是有10個主服務器,每一個服務器有1個從服務器,2個主服務器各有5個從服務器,那麼將嘗試遷移的從服務器是 - 在具備5個從服務器的2個主服務器中 - 具備最低節點ID的從服務器。鑑於沒有使用協議,當集羣配置不穩定時,可能會出現競爭條件,其中多個從屬設備認爲本身是具備較低節點ID的非故障從設備(在實踐中不太可能發生這種狀況) )。若是發生這種狀況,結果是多個從屬服務器遷移到同一個主服務器,這是無害的。若是比賽發生的方式會使分出的master沒有slave,但在最終每一個master都將獲得至少一個slave的支持。可是,正常行爲是單個從設備從具備多個從設備的主設備遷移到孤立主設備。
該算法由用戶可配置的參數控制,該參數稱爲 cluster-migration-barrier
:在從屬設備遷移以前必須留下主設備的良好從設備的數量。例如,若是此參數設置爲2,則只有在其主服務器保留兩個工做從服務器時,服務器才能嘗試遷移。
在故障轉移期間經過slave promotion建立configEpoch
新值時,它們將保證是惟一的。
可是,有兩個不一樣的事件,其中新的configEpoch值以不安全的方式建立,只是遞增本地節點的本地currentEpoch
並但願同時沒有衝突。這兩個事件都是系統管理員觸發的:
TAKEOVER
選項的CLUSTER FAILOVER命令可以手動將從節點提高爲主節點,而無需大多數主節點可用。例如,這在多數據中心設置中頗有用。具體來講,在手動從新分片期間,當散列槽從節點A遷移到節點B時,從新分片程序將強制B將其配置升級到集羣中發現的最大的時期加1(除非節點是已是具備最大configEpoch的那個),而不須要來自其餘節點的協議。一般,真實世界的從新分片涉及移動數百個散列槽(特別是在小簇中)。對於每一個移動的散列槽,要求每次在從新分片期間生成新configEpoch的協議是低效的。此外,每次在resharding期間,都要讓每一個集羣節點中用fsync來存儲新配置。由於它的執行方式,咱們能夠用更好的方法代替,咱們只須要在第一個hash slot被移動時,使用新的configEpoch ,這更有效。
然而,因爲上述兩種狀況,有可能(儘管不太可能)以具備相同configEpoch的多個節點結束。系統管理員執行的從新分片操做以及同時發生的故障轉移(加上不少壞運氣)currentEpoch
若是傳播速度不夠快,可能會致使衝突。
此外,軟件錯誤和文件系統損壞也可能致使具備相同配置時期的多個節點。
當服務於不一樣散列槽的主服務器具備相同的configEpoch
時,沒有問題。更重要的是,slave failing over master具備惟一的configEpoch。
也就是說,手動干預或從新分配可能會以不一樣方式更改集羣配置。Redis Cluster主要活動屬性要求插槽配置始終收斂,所以在任何狀況下咱們都但願全部主節點都有不一樣的configEpoch
。
爲了強制執行此操做,在兩個節點以相同的configEpoch
方式結束時使用衝突解決算法。
configEpoch
。configEpoch
節點ID相比,節點具備按字典順序更小的節點ID。currentEpoch
1,並將其用做新的configEpoch
。若是有任何一組節點具備相同configEpoch
的節點,那麼除了具備最大節點ID的節點以外的全部節點都將向前移動,從而保證每一個節點最終將選擇惟一的configEpoch而無論發生了什麼。
此機制還保證在建立新集羣后,全部節點都以不一樣的方式啓動configEpoch
(即便實際上並未使用),由於redis-trib
確保CONFIG SET-CONFIG-EPOCH
在啓動時使用。可是,若是因爲某種緣由致使節點配置錯誤,它將自動將其配置更新爲不一樣的configEpoch。
節點能夠經過軟件重置(無需從新啓動),以便在不一樣的角色或不一樣的集羣中重複使用。這在正常操做,測試和雲環境中很是有用,在這些環境中,能夠從新配置給定節點以加入不一樣的節點集以放大或建立新集羣。
在Redis集羣中,使用CLUSTER RESET命令重置節點。該命令有兩種變體:
CLUSTER RESET SOFT
CLUSTER RESET HARD
必須將命令直接發送到節點才能重置。若是未提供復位類型,則執行軟復位。
如下是重置執行的操做列表:
currentEpoch
,configEpoch
和lastVoteEpoch
設置爲0。沒法重置具備非空數據集的主節點(由於一般您但願將數據從新硬化到其餘節點)。可是,在適當的特殊條件下(例如,當爲了建立新集羣而徹底銷燬集羣時),必須在繼續復位以前執行FLUSHALL。
經過將其全部數據從新分配給其餘節點(若是它是主節點)並將其關閉,實際上能夠從現有集羣中刪除節點。可是,其餘節點仍將記住其節點ID和地址,並將嘗試與其鏈接。
所以,當刪除節點時,咱們還但願從全部其餘節點表中刪除其條目。這是經過使用該CLUSTER FORGET <node-id>
命令完成的 。
該命令有兩個做用:
第二個操做是必需的,由於Redis Cluster使用gossip來自動發現節點,所以從節點A移除節點X可能致使節點B再次將節點X gossip 到A節點。因爲禁止60秒,Redis集羣管理工具備60秒,以便從全部節點中刪除節點,從而防止因爲自動發現而從新添加節點。
有關詳細信息,請參閱CLUSTER FORGET文檔。
問題:publish在集羣每一個節點廣播:加劇帶寬。
解決:單獨使用一套Redis Sentinel。
1:節點和槽分配不均。
2:不一樣槽對應鍵值數量差別較大。
3:包含bigkey。
4:內存相關配置不一致。
熱點key:重要的key或者bigkey。
優化:
一、避免bigkey
二、熱鍵不要用hash_tag
三、當一致性不高時,能夠用本地緩存+MQ
只讀鏈接:集羣模式的從節點不接受任何讀寫請求。
1:重定向到負責槽的主節點。
2:readonly命令能夠讀:鏈接級別的命令。
讀寫分離:更加複雜。
1:一樣的問題:複製延遲、讀取過時數據、從節點故障。
2:修改客戶端:cluster slaves {nodeId}
在線遷移的一些工具:
1:惟品會:redis-migrate-tool。
2:豌豆莢:redis-port。
1:key批量操做支持有限:例如mget、mset必須在一個slot。
2:key事務和Lua支持有限:操做的key必須在一個節點。
3:key是數據分區的最小粒度:不支持bigkey分區。
4:不支持多個數據庫:集羣模式下只有一個db 0。
5:複製只支持一層:不支持樹形複製結構。
1:Redis Cluster:知足容量和性能的擴展性,不少業務「不須要」。
2:不少場景Redis Sentinel已經足夠好。
1)Redis cluster數據分區規則採用虛擬槽方式(16384個槽),每一個結點負責一部分槽和相關數據,實現數據和請求的負載均衡。
2)搭建集羣劃分四個步驟:準備節點、節點握手、分配槽、複製。
3)集羣伸縮經過在節點之間移動槽和相關數據實現。
4)使用smart客戶端操做集羣達到通訊效率最大化,客戶端內部負責計算維護鍵->槽->節點的映射,用於快速定位到目標節點。
5)集羣自動故障轉移過程分爲故障發現和節點恢復。節點下線分爲主觀下線和客觀下線,當超過半數主節點認爲故障節點爲主觀下線時標記它爲客觀下線狀態。從節點負責對客觀下線的主節點觸發故障恢復流程,保證集羣的可用性。