1、Redis Cluster主要特性和設計java
集羣目標node
1)高性能和線性擴展,最大能夠支撐到1000個節點;Cluster架構中無Proxy層,Master與slave之間使用異步replication,且不存在操做的merge。(即操做不能跨多個nodes,不存在merge層)redis
2)必定程度上保證writes的安全性,須要客戶端容忍必定程度的數據丟失:集羣將會盡量(best-effort)保存客戶端write操做的數據;一般在failover期間,會有短暫時間內的數據丟失(由於異步replication引發);當客戶端與少數派的節點處於網絡分區時(network partition),丟失數據的可能性會更高。(由於節點有效性檢測、failover須要更長的時間)算法
3)可用性:只要集羣中大多數master可達、且失效的master至少有一個slave可達時,集羣均可以繼續提供服務;同時「replicas migration」能夠將那些擁有多個slaves的master的某個slave,遷移到沒有slave的master下,即將slaves的分佈在整個集羣相對平衡,盡力確保每一個master都有必定數量的slave備份。mongodb
(Redis Cluster集羣有多個shard組成,每一個shard能夠有一個master和多個slaves構成,數據根據hash slots配額分佈在多個shard節點上,節點之間創建雙向TCP連接用於有效性檢測、Failover等,Client直接與shard節點進行通信;Cluster集羣沒有Proxy層,也沒有中央式的Master用於協調集羣狀態或者state存儲;集羣暫不提供動態reblance策略)緩存
備註:下文中提到的query、查詢等語義,泛指redis的讀寫操做。安全
Mutli-key操做網絡
Redis單實例支持的命令,Cluster也都支持,可是對於「multi-key」操做(即一次RPC調用中須要進行多個key的操做)好比Set類型的交集、並集等,則要求這些key必須屬於同一個node。Cluster不能進行跨Nodes操做,也沒有nodes提供merge層代理。架構
Cluster中實現了一個稱爲「hash tags」的概念,每一個key均可以包含一個自定義的「tags」,那麼在存儲時將根據tags計算此key應該分佈在哪一個nodes上(而不是使用key計算,可是存儲層面仍然是key);此特性,能夠強制某些keys被保存在同一個節點上,以便於進行「multikey」操做,好比「foo」和「{foo}.student」將會被保存在同一個node上。不過在人工對slots進行resharding期間,multikey操做可能不可用。app
咱們在Redis單例中,偶爾會用到「SELECT」指令,便可以將key保存在特定的database中(默認database索引號爲0);可是在Cluster環境下,將不支持SELECT命令,全部的key都將保存在默認的database中。
客戶端與Server角色
集羣中nodes負責存儲數據,保持集羣的狀態,包括keys與nodes的對應關係(內部其實爲slots與nodes對應關係)。nodes也可以自動發現其餘的nodes,檢測失效的節點,當某個master失效時還應該能將合適的slave提高爲master。
爲了達成這些行爲,集羣中的每一個節點都經過TCP與其餘全部nodes創建鏈接,它們之間的通訊協議和方式稱爲「Redis Cluster Bus」。Nodes之間使用gossip協議(參見下文備註)向其餘nodes傳播集羣信息,以達到自動發現的特性,經過發送ping來確認其餘nodes工做正常,也會在合適的時機發送集羣的信息。固然在Failover時(包括人爲failover)也會使用Bus來傳播消息。
gossip:最終一致性,分佈式服務數據同步算法,node首選須要知道(能夠讀取配置)集羣中至少一個seed node,此node向seed發送ping請求,此時seed節點pong返回本身已知的全部nodes列表,而後node解析nodes列表並與它們都創建tcp鏈接,同時也會向每一個nodes發送ping,並從它們的pong結果中merge出全局nodes列表,並逐步與全部的nodes創建鏈接.......數據傳輸的方式也是相似,網絡拓撲結構爲full mesh
由於Node並不提供Proxy機制,當Client將請求發給錯誤的nodes時(此node上不存在此key所屬的slot),node將會反饋「MOVED」或者「ASK」錯誤信息,以便Client從新定向到合適的node。理論上,Client能夠將請求發送給任意一個nodes,而後根據在根據錯誤信息轉發給合適的node,客戶端能夠不用保存集羣的狀態信息,固然這種狀況下性能比較低效,由於Client可能須要2次TCP調用才能獲取key的結果,一般客戶端會緩存集羣中nodes與slots的映射關係,並在遇到「Redirected」錯誤反饋時,纔會更新本地的緩存。
安全寫入(write safety)
在Master-slaves之間使用異步replication機制,在failover以後,新的Master將會最終替代其餘的replicas(即slave)。在出現網絡分區時(network partition),總會有一個窗口期(node timeout)可能會致使數據丟失;不過,Client與多數派的Master、少數派Master處於一個分區(網絡分區,由於網絡阻斷問題,致使Clients與Nodes被隔離成2部分)時,這兩種狀況下影響並不相同。
1)write提交到master,master執行完畢後向Client反饋「OK」,不過此時可能數據尚未傳播給slaves(異步replication);若是此時master不可達的時間超過閥值(node timeout,參見配置參數),那麼將觸發slave被選舉爲新的Master(即Failover),這意味着那些沒有replication到slaves的writes將永遠丟失了!
2)還有一種狀況致使數據丟失:
A)由於網絡分區,此時master不可達,且Master與Client處於一個分區,且是少數派分區。
B)Failover機制,將其中一個slave提高爲新Master。
C)此後網絡分區消除,舊的Master再次可達,此時它將被切換成slave。
D)那麼在網絡分區期間,處於少數派分區的Client仍然將write提交到舊的Master,由於它們以爲Master仍然有效;當舊的Master再次加入集羣,切換成slave以後,這些數據將永遠丟失。
在第二種狀況下,若是Master沒法與其餘大多數Masters通信的時間超過閥值後,此Master也將再也不接收Writes,自動切換爲readonly狀態。當網絡分區消除後,仍然會有一小段時間,客戶端的write請求被拒絕,由於此時舊的Master須要更新本地的集羣狀態、與其餘節點創建鏈接、角色切換爲slave等等,同時Client端的路由信息也須要更新。
只有當此master被大多數其餘master不可達的時間達到閥值時,纔會觸發Failover,這個時間稱爲NODE_TIMEOUT,能夠經過配置設定。因此當網絡分區在此時間被消除的話,writes不會有任何丟失。反之,若是網絡分區持續時間超過此值,處於「小分區」(minority)端的Master將會切換爲readonly狀態,拒絕客戶端繼續提交writes請求,那麼「大分區」端將會進行failover,這意味着NODE_TIMEOUT期間發生在「小分區」端的writes操做將丟失(由於新的Master上沒有同步到那些數據)。
可用性
處於「小分區」的集羣節點是不可用的;「大分區」端必須持有大多數Masters,同時每一個不可達的Master至少有一個slave也在「大分區」端,當NODE_TIMEOUT時,觸發failover,此後集羣纔是可用的。Redis Cluster在小部分nodes失效後仍然能夠恢復有效性,若是application但願大面積節點失效仍然有效,那麼Cluster不適合這種狀況。
好比集羣有N個Master,且每一個Master都有一個slave,那麼集羣的有效性只能容忍一個節點(master)被分區隔離(即一個master處於小分區端,其餘處於大分區端),當第二個節點被分區隔離以前仍保持可用性的機率爲1 - (1/(N * 2 - 1))(解釋:當第一個節點失效後,剩餘N * 2 -1個節點,此時沒有slave的Master失效的機率爲1/(N * 2 -1))。好比有5個Master,每一個Master有一個slave,當2個nodes被隔離出去(或者失效)後,集羣可用性的機率只有1/(5 * 2 - 1) = 11.11%,所以集羣再也不可用。
幸虧Redis Cluster提供了「replicas migration」機制,在實際應用方面,能夠有效的提升集羣的可用性,當每次failover發生後,集羣都會從新配置、平衡slaves的分佈,以更好的抵禦下一次失效狀況的發生。(具體參見下文)
性能
Redis Cluster並無提供Proxy層,而是告知客戶端將key的請求轉發給合適的nodes。Client保存集羣中nodes與keys的映射關係(slots),並保持此數據的更新,因此一般Client總可以將請求直接發送到正確的nodes上。由於採用異步replication,因此master不會等待slaves也保存成功後才向客戶端反饋結果,除非顯式的指定了WAIT指令。multi-key指令僅限於單個節點內,除了resharding操做外,節點的數據不會在節點間遷移。每一個操做只會在特定的一個節點上執行,因此集羣的性能爲master節點的線性擴展。同時,Clients與每一個nodes保持連接,因此請求的延遲等同於單個節點,即請求的延遲並不會由於Cluster的規模增大而受到影響。高性能和擴展性,同時保持合理的數據安全性,是Redis Cluster的設計目標。
Redis Cluster沒有Proxy層,Client請求的數據也沒法在nodes間merge;由於Redis核心就是K-V數據存儲,沒有scan類型(sort,limit,group by)的操做,所以merge操做並不被Redis Cluster所接受,並且這種特性會極大增長了Cluster的設計複雜度。(類比於mongodb)
2、Cluster主要組件
keys分佈模型
集羣將key分紅16384個slots(hash 槽),slot是數據映射的單位,言外之意,Redis Cluster最多支持16384個nodes(每一個nodes持有一個slot)。集羣中的每一個master持有16384個slots中的一部分,處於「stable」狀態時,集羣中沒有任何slots在節點間遷移,即任意一個hash slot只會被單個node所服務(master,固然能夠有多個slave用於replicas,slave也能夠用來擴展read請求)。keys與slot的映射關係,是按照以下算法計算的:HASH_SLOT = CRC16(key) mod 16384。其中CRC16是一種冗餘碼校驗和,能夠將字符串轉換成16位的數字。
hash tags
在計算hash slots時有一個意外的狀況,用於支持「hash tags」;hash tags用於確保多個keys可以被分配在同一個hash slot中,用於支持multi-key操做。hash tags的實現比較簡單,key中「{}」之間的字符串就是當前key的hash tags,若是存在多個「{}」,首個符合規則的字符串做爲hash tags,若是「{}」存在多級嵌套,那麼最內層首個完整的字符串做爲hash tags,好比「{foo}.student」,那麼「foo」是hash tags。若是key中存在合法的hash tags,那麼在計算hash slots時,將使用hash tags,而再也不使用原始的key。即「foo」與「{foo}.student」將獲得相同的slot值,不過「{foo}.student」仍做爲key來保存數據,即redis中數據的key仍爲「{foo}.student」。
集羣節點的屬性
集羣中每一個節點都有惟一的名字,稱之爲node ID,一個160位隨機數字的16進製表示,在每一個節點首次啓動時建立。每一個節點都將各自的ID保存在實例的配置文件中,此後將一直使用此ID,或者說只要配置文件不被刪除,或者沒有使用「CLUSTER RESET」指令重置集羣,那麼此ID將永不會修改。
集羣經過node ID來標識節點,而不是使用IP + port,由於node能夠修改它的IP和port,不過若是ID不變,咱們仍然認定它是集羣中合法一員。集羣能夠在cluster Bus中經過gossip協議來探測IP、port的變動,並從新配置。
node ID並非與node相關的惟一信息,不過是惟一一個全局一致的。每一個node還持有以下相關的信息,有些信息是關係集羣配置的,其餘的信息好比最後ping時間等。每一個node也保存其餘節點的IP、Port、flags(好比flags表示它是master仍是slave)、最近ping的時間、最近pong接收時間、當前配置的epoch、連接的狀態,最重要的是還包含此node上持有的hash slots。這些信息都可經過「CLUSTER NODES」指令開查看。
Cluster Bus
每一個Node都有一個特定的TCP端口,用來接收其餘nodes的連接;此端口號爲面向Client的端口號 + 10000,好比果客戶端端口號爲6379,那麼次node的BUS端口號爲16379,客戶端端口號能夠在配置文件中聲明。因而可知,nodes之間的交互通信是經過Bus端口進行,使用了特定的二進制協議,此端口一般應該只對nodes可用,能夠藉助防火牆技術來屏蔽其餘非法訪問。
集羣拓撲
Redis Cluster中每一個node都與其餘nodes的Bus端口創建TCP連接(full mesh,全網)。好比在由N各節點的集羣中,每一個node有N-1個向外發出的TCP連接,以及N-1個其餘nodes發過來的TCP連接;這些TCP連接老是keepalive,不是按需建立的。若是ping發出以後,node在足夠長的時間內仍然沒有pong響應,那麼次node將會被標記爲「不可達」,那麼與此node的連接將會被刷新或者重建。Nodes之間經過gossip協議和配置更新的機制,來避免每次都交互大量的消息,最終確保在nodes之間的信息傳送量是可控的。
節點間handshake
Nodes經過Bus端口發送ping、pong;若是一個節點不屬於集羣,那麼它的消息將會被其餘nodes所有丟棄。一個節點被認爲是集羣成員的方式有2種:
1)若是此node在「Cluster meet」指令中引入,此命令的主要意義就是將指定node加入集羣。那麼對於當前節點,將認爲指定的node爲「可信任的」。(此後將會經過gossip協議傳播給其餘nodes)
2)當其餘nodes經過gossip引入了新的nodes,這些nodes也是被認爲是「可信任的」。
只要咱們將一個節點加入集羣,最終此節點將會與其餘節點創建連接,即cluster能夠經過信息交換來自動發現新的節點,連接拓撲仍然是full mesh。
3、重定向與resharding
MOVED重定向
理論上,Client能夠將請求隨意發給任何一個node,包括slaves,此node解析query,若是能夠執行(好比語法正確,multiple keys都應該在一個node slots上),它會查看key應該屬於哪一個slot、以及此slot所在的nodes,若是當前node持有此slot,那麼query直接執行便可,不然當前node將會向Client反饋「MOVED」錯誤:
GET X -MOVED 3999 127.0.0.1:6381
錯誤信息中包括此key對應的slot(3999),以及此slot所在node的ip和port,對於Client 而言,收到MOVED信息後,它須要將請求從新發給指定的node。不過,當node向Client返回MOVED以前,集羣的配置也在變動(節點調整、resharding、failover等,可能會致使slot的位置發生變動),此時Client可能須要等待更長的時間,不過最終node會反饋MOVED信息,且信息中包含指定的新的node位置。雖然Cluster使用ID標識node,可是在MOVED信息中儘量的暴露給客戶端便於使用的ip + port。
當Client遇到「MOVED」錯誤時,將會使用「CLUSTER NODES」或者「CLUSTER SLOTS」指令獲取集羣的最新信息,主要是nodes與slots的映射關係;由於遇到MOVED,通常也不會僅僅一個slot發生的變動,一般是一個或者多個節點的slots發生了變化,因此進行一次全局刷新是有必要的;咱們還應該明白,Client將會把集羣的這些信息在被緩存,以便提升query的性能。
還有一個錯誤信息:「ASK」,它與「MOVED」都屬於重定向錯誤,客戶端的處理機制基本相同,只是ASK不會觸發Client刷新本地的集羣信息。
集羣運行時從新配置(live reconfiguration)
咱們能夠在Cluster運行時增長、刪除nodes,這兩種操做都會致使:slots在nodes的遷移;固然這種機制也可用來集羣數據的rebalance等等。
1)集羣中新增一個node,咱們須要將其餘nodes上的部分slots遷移到此新nodes上,以實現數據負載的均衡分配。
2)集羣中移除一個node,那麼在移除節點以前,必須將此節點上(若是此節點沒有任何slaves)的slots遷移到其餘nodes。
3)若是數據負載不均衡,好比某些slots數據集較大、負載較大時,咱們須要它們遷移到負載較小的nodes上(即手動resharding),以實現集羣的負載平衡。
Cluster支持slots在nodes間移動;從實際的角度來看,一個slot只是一序列keys的邏輯標識,因此Cluster中slot的遷移,其實就是一序列keys的遷移,不過resharding操做只能以slot爲單位(而不能僅僅遷移某些keys)。Redis提供了以下幾個操做:
1)CLUSTER ADDSLOTS [slot] ....
2)CLUSTER DELSLOTS [slot] ...
3)CLUSTER SETSLOT [slot] NODE [node]
4)CLUSTER SETSLOT [slot] MIGRATING [destination-node]
5)CLUSTER SETSLOT [slot] IMPORTING [source-node]
前兩個指令:ADDSLOTS和DELSLOTS,用於向當前node分配或者移除slots,指令能夠接受多個slot值。分配slots的意思是告知指定的master(即此指令須要在某個master節點執行)此後由它接管相應slots的服務;slots分配後,這些信息將會經過gossip發給集羣的其餘nodes。
ADDSLOTS指令一般在建立一個新的Cluster時使用,一個新的Cluster有多個空的Masters構成,此後管理員須要手動爲每一個master分配slots,並將16384個slots分配完畢,集羣才能正常服務。簡而言之,ADDSLOTS只能操做那些還沒有分配的(即不被任何nodes持有)slots,咱們一般在建立新的集羣或者修復一個broken的集羣(集羣中某些slots由於nodes的永久失效而丟失)時使用。爲了不出錯,Redis Cluster提供了一個redis-trib輔助工具,方便咱們作這些事情。
DELSLOTS就是將指定的slots刪除,前提是這些slots必須在當前node上,被刪除的slots處於「未分配」狀態(固然其對應的keys數據也被clear),即還沒有被任何nodes覆蓋,這種狀況可能致使集羣處於不可用狀態,此指令一般用於debug,在實際環境中不多使用。那些被刪除的slots,能夠經過ADDSLOTS從新分配。
SETSLOT是個很重要的指令,對集羣slots進行reshard的最重要手段;它用來將單個slot在兩個nodes間遷移。根據slot的操做方式,它有兩種狀態「MIGRATING」、「IMPORTING」(或者說遷移的方式)
1)MIGRATING:將slot的狀態設置爲「MIGRATING」,並遷移到destination-node上,須要注意當前node必須是slot的持有者。在遷移期間,Client的查詢操做仍在當前node上執行,若是key不存在,則會向Client反饋「-ASK」重定向信息,此後Client將會把請求從新提交給遷移的目標node。
2)IMPORTING:將slot的狀態設置爲「IMPORTING」,並將其從source-node遷移到當前node上,前提是source-node必須是slot的持有者。Client交互機制同上。
假如咱們有兩個節點A、B,其中slot 8在A上,咱們但願將8從A遷移到B,可使用以下方式:
1)在B上:CLUSTER SETSLOT 8 IMPORTING A
2)在A上:CLUSTER SETSLOT 8 MIGRATING B
在遷移期間,集羣中其餘的nodes的集羣信息不會改變,即slot 8仍對應A,即此期間,Client查詢仍在A上:
1)若是key在A上存在,則有A執行。
2)不然,將向客戶端返回ASK,客戶端將請求重定向到B。
這種方式下,新key的建立就不會在A上執行,而是在B上執行,這也就是ASK重定向的緣由(遷移以前的keys在A,遷移期間created的keys在B上);當上述SETSLOT執行完畢後,slot的狀態也會被自動清除,同時將slot遷移信息傳播給其餘nodes,至此集羣中slot的映射關係將會變動,此後slot 8的數據請求將會直接提交到B上。
ASK重定向
在上文中,咱們已經介紹了MOVED重定向,ASK與其很是類似。在resharding期間,爲何不能用MOVED?MOVED意思爲hash slots已經永久被另外一個node接管、接下來的相應的查詢應該與它交互,ASK的意思是當前query暫時與指定的node交互;在遷移期間,slot 8的keys有可能仍在A上,因此Client的請求仍然須要首先經由A,對於A上不存在的,咱們才須要到B上進行嘗試。遷移期間,Redis Cluster並無粗暴的將slot 8的請求所有阻塞、直到遷移結束,這種方式儘管再也不須要ASK,可是會影響集羣的可用性。
1)當Client接收到ASK重定向,它僅僅將當前query重定向到指定的node;此後的請求仍然交付給舊的節點。
2)客戶端並不會更新本地的slots映射,仍然保持slot 8與A的映射;直到集羣遷移完畢,且遇到MOVED重定向。
一旦slot 8遷移完畢以後(集羣的映射信息也已更新),若是Client再次在A上訪問slot 8時,將會獲得MOVED重定向信息,此後客戶端也更新本地的集羣映射信息。
客戶端首次連接以及重定向處理
可能有些Cluster客戶端的實現,不會在內存中保存slots映射關係(即nodes與slots的關係),每次請求都從聲明的、已知的nodes中,隨機訪問一個node,並根據重定向(MOVED)信息來尋找合適的node,這種訪問模式,一般是很是低效的。
固然,Client應該儘量的將slots配置信息緩存在本地,不過配置信息也不須要絕對的實時更新,由於在請求時偶爾出現「重定向」,Client也能兼容這次請求的正確轉發,此時再更新slots配置。(因此Client一般不須要間歇性的檢測Cluster中配置信息是否已經更新)客戶端一般是全量更新slots配置:
1)首次連接到集羣的某個節點
2)當遇到MOVED重定向消息時
遇到MOVED時,客戶端僅僅更新特定的slot是不夠的,由於集羣中的reshard一般會影響到多個slots。客戶端經過向任意一個nodes發送「CLUSTER NODES」或者「CLUSTER SLOTS」指令都可以得到當前集羣最新的slots映射信息;「CLUSTER SLOTS」指令返回的信息更易於Client解析。若是集羣處於broken狀態,即某些slots還沒有被任何nodes覆蓋,指令返回的結果多是不完整的。
Multikeys操做
前文已經介紹,基於hash tags機制,咱們能夠在集羣中使用Multikeys操做。不過,在resharding期間,Multikeys操做將可能不可用,好比這些keys不存在於同一個slot(遷移會致使keys被分離);好比Multikeys邏輯上屬於同一個slot,可是由於resharding,它們可能暫時不處於同一個nodes,有些可能在遷移的目標節點上(好比Multikeys包含a、b、c三個keys,邏輯上它們都屬於slot 8,可是其中c在遷移期間建立,它被存儲在節點B上,a、b仍然在節點A),此時將會向客戶端返回「-TRYAGAIN」錯誤,那麼客戶端此後將須要重試一次,或者直接返回錯誤(若是遷移操做被中斷),不管如何最終Multikeys的訪問邏輯是一致的,slots的狀態也是最終肯定的。
slaves擴展reads請求
一般狀況下,read、write請求都將有持有slots的master節點處理;由於redis的slaves能夠支持read操做(前提是application可以容忍stale數據),因此客戶端可使用「READONLY」指令來擴展read請求。
「READONLY」代表其能夠訪問集羣的slaves節點,可以容忍stale數據,並且這次連接不會執行writes操做。當連接設定爲readonly模式後,Cluster只有當keys不被slave的master節點持有時纔會發送重定向消息(即Client的read請求老是發給slave,只有當此slave的master不持有slots時纔會重定向,很好理解):
1)此slave的master節點不持有相應的slots
2)集羣從新配置,好比reshard或者slave遷移到了其餘master上,此slave自己也不持有此slot。
此時Client更新本地的slot配置信息,同上文所述。(目前不少Client實現均基於鏈接池,因此不能很是便捷的設置READLONLY選項,很是遺憾)
4、容錯(Fault Tolerance)
心跳與gossip消息
集羣中的nodes持續的交換ping、pong數據,這兩種數據包的結構同樣,一樣都能攜帶集羣的配置信息,惟一不一樣的就是message中的type字段。
一般,一個node發送ping消息,那麼接收者將會反饋pong消息;不過有時候並不是如此,或許接收者將pong信息發給其餘的nodes,而不是直接反饋給發送者,好比當集羣中添加新的node時。
一般一個node每秒都會隨機向幾個nodes發送ping,因此不管集羣規模多大,每一個nodes發送的ping數據包的總量是恆定的。每一個node都確保儘量的向那些在半個NODE_TIMEOUT時間內,還沒有發送過ping或者接收到它們的pong消息的nodes發送ping。在NODE_TIMEOUT逾期以前,nodes也會嘗試與那些通信異常的nodes從新創建TCP連接,確保不能僅僅由於當前連接異常而認爲它們就是不可達的。
當NODE_TIMEOUT值較小、集羣中nodes規模較大時,那麼全局交換的信息量也會很是龐大,由於每一個node都盡力在半個NODE_TIMEOUT時間內,向其餘nodes發送ping。好比有100個nodes,NODE_TIMEOUT爲60秒,那麼每一個node在30秒內向其餘99各nodes發送ping,平均每秒3.3個消息,那麼整個集羣全局就是每秒330個消息。這些消息量,並不會對集羣的帶寬帶來不良問題。
心跳數據包的內容
1)node ID
2)currentEpoch和configEpoch
3)node flags:好比表示此node是maste、slave等
4)hash slots:發送者持有的slots
5)若是發送者是slave,那麼其master的ID
6)其餘..
ping和pong數據包中也包含gossip部分,這部分信息包含sender持有的集羣視圖,不過它只包含sender已知的隨機幾個nodes,nodes的數量根據集羣規模的大小按比例計算。gossip部分包含了nodes的ID、ip+port、flags,那麼接收者將根據sender的視圖,來斷定節點的狀態,這對故障檢測、節點自動發現很是有用。
失效檢測
集羣失效檢測就是,當某個master或者slave不能被大多數nodes可達時,用於故障遷移並將合適的slave提高爲master。當slave提高未能有效實施時,集羣將處於error狀態且中止接收Client端查詢。
如上所述,每一個node有持有其已知nodes的列表包括flags,有2個flag狀態:PFAIL和FAIL;PFAIL表示「可能失效」,是一種還沒有徹底確認的失效狀態(即某個節點或者少數masters認爲其不可達)。FAIL表示此node已經被集羣大多數masters斷定爲失效(大多數master已認定爲不可達,且不可達時間已達到設定值,須要failover)。
PFAIL:
一個被標記爲PFAIL的節點,表示此node不可達的時間超過NODE_TIMEOUT,master和slave有能夠被標記爲PFAIL。所謂不可達,就是當「active ping」(發送ping且能受到pong)還沒有成功的時間超過NODE_TIMEOUT,所以咱們設定的NODE_TIMEOUT的值應該比網絡交互往返的時間延遲要大一些(一般要大的多,以致於交互往返時間能夠忽略)。爲了不誤判,當一個node在半個NODE_TIMEOUT時間內仍未能pong,那麼當前node將會盡力嘗試從新創建鏈接進行重試,以排除pong未能接收是由於當前連接故障的問題。
FAIL:
PFAIL只是當前node有關於其餘nodes的本地視圖,可能每一個node對其餘nodes的本地視圖都不同,因此PFAIL還不足以觸發Failover。處於PFAIL狀態下的node能夠被提高到FAIL狀態。如上所述,每一個node在向其餘nodes發送gossip消息時,都會包含本地視圖中幾個隨機nodes的狀態信息;每一個node最終都會從其餘nodes發送的消息中得到一組nodes的flags。所以,每一個node均可以經過這種機制來通知其餘nodes,它檢測到的故障狀況。
PFAIL被上升爲FAIL的集中狀況:
1)好比A節點,認爲B爲PFAIL
2)那麼A經過gossip信息,收集集羣中大多數masters關於B的狀態視圖。
3)多數master都認爲B爲PFAIL,或者PFAIL狀況持續時間爲NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT(此值當前爲2)
若是上述條件成立,那麼A將會:
1)將B節點設定爲FAIL
2)將FAIL信息發送給其全部能到達的全部節點。
每一個接收到FAIL消息的節點都會強制將此node標記爲FAIL狀態,無論此節點在本地視圖中是否爲PFAIL。FAIL狀態是單向的,即PFAIL能夠轉換爲FAIL,可是FAIL狀態只能清除,不能迴轉爲PFAIL:
1)當此node已經變的可達,且爲slave,這種狀況下FAIL狀態將會被清除,由於沒有發生failover。
2)此node已經可達,且是一個沒有服務任何slots的master(空的master);這種狀況下,FAIL將會被清除,由於master沒有持有slots,因此它並無真正參與到集羣中,須要等到從新配置以便它加入集羣。
3)此node已經可達,且是master,且在較長時間內(N倍的NODE_TIMEOUT)沒有檢測到slave的提高,即沒有slave發生failover(好比此master下沒有slave),那麼它只能從新加入集羣且仍爲master。
須要注意的是PFAIL->FAIL的轉變,使用了「協議」(agreement)的形式:
1)nodes會間歇性的收集其餘nodes的視圖,即便大多數masters都「agree」,事實上這個狀態,僅僅是咱們從不一樣的nodes、不一樣的時間收集到的,咱們沒法確認(也不須要)在特定時刻大多數masters是否「agree」。咱們丟棄較舊的故障報告,因此此故障(FAIL)是有大多數masters在一段時間內的信號。
2)雖然每一個node在檢測到FAIL狀況時,都會經過FAIL消息發送給其餘nodes,可是沒法保證消息必定會到達全部的nodes,好比可能當前節點(發送消息的node)由於網絡分區與其餘部分nodes隔離了。
若是隻有少數master認爲某個node爲FAIL,並不會觸發相應的slave提高,即failover,由於多是由於網絡分區致使。FAIL標記只是用來觸發slave 提高;在原理上,當master不可達時將會觸發slave提高,不過當master仍然被大多數可達時,它會拒絕提供相應的確認。
5、Failover相關的配置
集羣currentEpoch
Redis Cluster使用了相似於Raft算法「term」(任期)的概念,那麼在redis Cluster中term稱爲epoch,用來給events增量版本號。當多個nodes提供了信息有衝突時,它能夠做爲node來知道哪一個狀態是最新的。currentEpoch爲一個64位無簽名數字。
在集羣node建立時,master和slave都會將各自的currentEpoch設置爲0,每次從其餘node接收到數據包時,若是發現發送者的epoch值比本身的大,那麼當前node將本身的currentEpoch設置爲發送者的epoch。由此,最終全部的nodes都會認同集羣中最大的epoch值;當集羣的狀態變動,或者node爲了執行某個行爲需求agreement時,都將須要epoch(傳遞或者比較)。
當前來講,只有在slave提高期間發生;currentEpoch爲集羣的邏輯時鐘(logical clock),指使持有較大值的獲勝。(currentEpoch,當前集羣已達成認同的epoch值,一般全部的nodes應該同樣)
configEpoch
每一個master總會在ping、pong數據包中攜帶本身的configEpoch以及它持有的slots列表。新建立的node,其configEpoch爲0,slaves經過遞增它們的configEpoch來替代失效的master,並嘗試得到其餘大多數master的受權(認同)。當slave被受權,一個新的configEpoch被生成,slave提高爲master且使用此configEpoch。
接下來介紹configEpoch幫助解決衝突,當不一樣的nodes宣稱有分歧的配置時。
slaves在ping、pong數據包中也會攜帶本身的configEpoch信息,不過這個epoch爲它與master在最近一次數據交換時,master的configEpoch。
每當節點發現configEpoch值變動時,都會將新值寫入nodes.conf文件,固然currentEpoch也也是如此。這兩個變量在寫入文件後會伴隨磁盤的fsync,持久寫入。嚴格來講,集羣中全部的master都持有惟一的configEpoch值。同一組master-slaves持有相同的configEpoch。
slave選舉與提高
在slaves節點中進行選舉,在其餘masters的幫助下進行投票,選舉出一個slave並提高爲master。當master處於FAIL狀態時,將會觸發slave的選舉。slaves都但願將本身提高爲master,此master的全部slaves均可以開啓選舉,不過最終只有一個slave獲勝。當以下狀況知足時,slave將會開始選舉:
1)當此slave的master處於FAIL狀態
2)此master持有非零個slots
3)此slave的replication連接與master斷開時間沒有超過設定值,爲了確保此被提高的slave的數據是新鮮的,這個時間用戶能夠配置。
爲了選舉,第一步,就是slave自增它的currentEpoch值,而後向其餘masters請求投票(需求支持,votes)。slave經過向其餘masters傳播「FAILOVER_AUTH_REQUEST」數據包,而後最長等待2倍的NODE_TIMEOUT時間,來接收反饋。一旦一個master向此slave投票,將會響應「FAILOVER_AUTH_ACK」,此後在2 * NODE_TIMOUT時間內,它將不會向同一個master的slaves投票;雖然這對保證安全上沒有必要,可是對避免多個slaves同時選舉時有幫助的。slave將會丟棄那些epoch值小於本身的currentEpoch的AUTH_ACK反饋,即不會對上一次選舉的投票計數(只對當前輪次的投票計數)。一旦此slave獲取了大多數master的ACKs,它將在這次選舉中獲勝;不然若是大多數master不可達(在2 * NODE_TIMEOUT)或者投票額不足,那麼它的選舉將會被中斷,那麼其餘的slave將會繼續嘗試。
slave rank(次序)
當master處於FAIL狀態時,slave將會隨機等待一段時間,而後才嘗試選舉,等待的時間:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
必定的延遲確保咱們等待FAIL狀態在集羣中傳播,不然slave當即嘗試選舉(不進行等待的話),不過此時其餘masters或許還沒有意識到FAIL狀態,可能會拒絕投票。
延遲的時間是隨機的,這用來「去同步」(desynchronize),避免slaves同時開始選舉。SLAVE_RANK表示此slave已經從master複製數據的總量的rank。當master失效時,slaves之間交換消息以儘量的構建rank,持有replication offset最新的rank爲0,第二最新的爲1,依次輪推。這種方式下,持有最新數據的slave將會首先發起選舉(理論上)。固然rank順序也不是嚴格執行的,若是一個持有較小rank的slave選舉失敗,其餘slaves將會稍後繼續。
一旦,slave選舉成功,它將獲取一個新的、惟一的、自增的configEpoch值,此值比集羣中任何masters持有的都要大,它開始宣稱本身是master,並經過ping、pong數據包傳播,並提供本身的新的configEpoch以及持有的slots列表。爲了加快其餘nodes的從新配置,pong數據包將會在集羣中廣播。當前node不可達的那些節點,它們能夠從其餘節點的ping或者pong中獲知信息(gossip),並從新配置。
其餘節點也會檢測到這個新的master和舊master持有相同的slots,且持有更高的configEpoch,此時也會更新本身的配置(epoch,以及master);舊master的slaves不只僅更新配置信息,也會從新配置並與新的master跟進(slave of)。
Masters響應slave的投票請求
當Master接收到slave的「FAILOVER_AUTH_REQUEST」請求後,開始投票,不過須要知足以下條件:
1)此master只會對指定的epoch投票一次,而且拒絕對舊的epoch投票:每一個master都持有一個lastVoteEpoch,將會拒絕AUTH_REQUEST中currentEpoch比lastVoteEpoch小的請求。當master響應投票時,將會把lastVoteEpoch保存在磁盤中。
2)此slave的master處於FAIL狀態時,master纔會投票。
3)若是slave的currentEpoch比此master的currentEpoch小,那麼AUTH_REQUEST將會被忽略。由於master只會響應那些與本身的currentEpoch相等的請求。若是同一個slave再此請求投票,持有已經增長的currentEpoch,它(slave)將保證舊的投票響應不能參與計票。
好比master的currentEpoch爲5,lastVoteEpoch爲1:
1)slave的currentEpoch爲3
2)slave在選舉開始時,使用epoch爲4(先自增),由於小於master的epoch,因此投票響應被延緩。
3)slave在一段時間後將從新選舉,使用epoch爲5(4 + 1,再次自增),此時master上延緩的響應發給slave,接收後視爲有效。
1)master在2 * NODE_TIMEOUT超時以前,不會對同一個master的slave再次投票。這並非嚴格須要,由於也不太可能兩個slave在相同的epoch下同時贏得選舉。不過,它確保當一個slave選舉成功後,它(slave)有一段緩衝時間來通知其餘的slaves,避免另外一個slave贏得了新的一輪的選擇,避免沒必要要的二次failover。
2)master並不會盡力選舉最合適的slave。當slave的master處於FAIL狀態,此master在當前任期(term)內並不投票,只是批准主動投票者(即master不發起選舉,只批准別人的投票)。最合適的slave應該在其餘slaves以前,首先發起選舉。
3)當master拒絕一個slave投票,並不會發出一個「否決」響應,而是簡單的忽略。
4)slave發送的configEpoch是其master的,還包括其master持有的slots;master不會向持有相同slots、但configEpoch只較低的slave投票。
Hash Slots配置傳播
Redis Cluster中重要的一部分就是傳播集羣中哪些節點上持有的哪些hash slots信息;不管是啓動一個新的集羣,仍是當master失效其slave提高後更新配置,這對它們都相當重要。有2種方式用於hash slot配置的傳播:
1)heartbeat 消息:發送者的ping、pong消息中,老是攜帶本身目前持有的slots信息,無論本身是master仍是slave。
2)UPDATE 消息:由於每一個心跳消息中會包含發送者的configEpoch和其持有的slots,若是接收者發現發送者的信息已經stale(好比發送者的configEpoch值小於持有相同slots的master的值),它會向發送者反饋新的配置信息(UPDATE),強制stale節點更新它。
當一個新的節點加入集羣,其本地的hash slots映射表將初始爲NULL,即每一個hash slot都沒有與任何節點綁定。
Rule 1:若是此node本地視圖中一個hash slot還沒有分配(設置爲NULL),而且有一個已知的node聲明持有它,那麼此node將會修改本地hash slot的映射表,將此slot與那個node關聯。slave的failover操做、reshard操做都會致使hash slots映射的變動,新的配置信息將會經過心跳在集羣中傳播。
Rule 2:若是此node的本地視圖中一個hash slot已經分配,而且一個已知的node也聲明持有它,且此node的configEpoch比當前slot關聯的master的configEpoch值更大,那麼此node將會把slot從新綁定到新的node上。根據此規則,最終集羣中全部的nodes都贊同那個持有聲明持有slot、且configEpoch最大值的nodes爲slot的持有者。
nodes如何從新加入集羣
node A被告知slot 一、2如今有node B接管,假如這兩個slots目前有A持有,且A只持有這兩個slots,那麼此後A將放棄這2個slots,成爲空的節點;此後A將會被從新配置,成爲其餘新master的slave。這個規則可能有些複雜,A離羣一段時間後從新加入集羣,此時A發現此前本身持有的slots已經被其餘多個nodes接管,好比slot 1被B接管,slot 2被C接管。
在從新配置時,最終此節點上的slots將會被清空,那個竊取本身最後一個slot的node,將成爲它的新master。
節點從新加入集羣,一般發生在failover以後,舊的master(也能夠爲slave)離羣,而後從新加入集羣。
Replica遷移
Redis Cluster實現了一個成爲「Replica migration」的概念,用來提高集羣的可用性。好比集羣中每一個master都有一個slave,當集羣中有一個master或者slave失效時,而不是master與它的slave同時失效,集羣仍然能夠繼續提供服務。
1)master A,有一個slave A1
2)master A失效,A1被提高爲master
3)一段時間後,A1也失效了,那麼此時集羣中沒有其餘的slave能夠接管服務,集羣將不能繼續服務。
若是masters與slaves之間的映射關係是固定的(fixed),提升集羣抗災能力的惟一方式,就是給每一個master增長更多的slaves,不過這種方式開支很大,須要更多的redis實例。
解決這個問題的方案,咱們能夠將集羣非對稱,且在運行時能夠動態調整master-slaves的佈局(而不是固定master-slaves的映射),好比集羣中有三個master A、B、C,它們對應的slave爲A一、B一、C一、C2,即C節點有2個slaves。「Replica遷移」能夠自動的從新配置slave,將其遷移到某個沒有slave的master下。
1)A失效,A1被提高爲master
2)此時A1沒有任何slave,可是C仍然有2個slave,此時C2被遷移到A1下,成爲A1的slave
3)此後某刻,A1失效,那麼C2將被提高爲master。集羣能夠繼續提供服務。
Replica遷移算法
遷移算法並無使用「agree」形式,而是使用一種算法來避免大規模遷移,這個算法確保最終每一個master至少有一個slave便可。起初,咱們先定義哪一個slave是良好的:一個良好的slave不能處於FAIL狀態。觸發時機爲,任何一個slave檢測到某個master沒有一個良好slave時。參與遷移的slave必須爲,持有最多slaves的master的其中一個slave,且不處於FAIL狀態,且持有最小的node ID。
好比有10個masters都持有一個slave,有2個masters各持有5個slaves,那麼遷移將會發生在持有5個slaves的masters中,且node ID最小的slave node上。咱們再也不使用「agreement」,不過也有可能當集羣的配置不夠穩定時,有一種競爭狀況的發生,即多個slaves都認爲它們本身的ID最小;若是這種狀況發生,結果就是可能多個slaves會遷移到同一個master下,不過這並無什麼害處,可是最壞的結果是致使原來的master遷出了全部的slaves,讓本身變得單一。可是遷移算法(進程)會在遷移完畢以後從新判斷,若是還沒有平衡,那麼將會從新遷移。
最終,每一個master最少持有一個slave;這個算法由用戶配置的「cluster-migration-barrier」,此配置參數表示一個master至少保留多少個slaves,其餘多餘的slaves能夠被遷出。此值一般爲1,若是設置爲2,表示一個master持有的slaves個數大於2時,多餘的slaves才能夠遷移到持有更少slaves的master下。
configEpoch衝突解決算法
在slave failover期間,會生成新的configEpoch值,須要保證惟一性。不過有2種不一樣的event會致使configEpoch的建立是不安全的:僅僅自增本地的currentEpoch並但願它不會發生衝突。這兩個事件有系統管理員觸發:
1)CLUSTER FAILOVER:這個指令,就是人爲的將某個slave提高爲master,而不須要要求大多數masters的投票參與。
2)slots的遷移,用於平衡集羣的數據分佈(reshard);此時本地的configEpoch也會修改,由於性能的考慮,這個過程也不須要「agreement」。
在手動reshard期間,當一個hash slot從A遷移到B,resharding程序將強制B更新本身的配置信息、epoch值也修改成集羣的最大值 + 1(除非B的configEpoch已是最大值),這種變動則不須要其餘nodes的agreement(注意與failover的原理不一樣)。一般每次resharding都會遷移多個slots,且有多個nodes參與,若是每一個slots遷移都須要agreement,才能生成新的epoch,這種性能是不好的,也不可取。咱們在首個slots遷移開始時,只會生成一個新的configEpoch,在遷移完畢後,將新的配置傳播給集羣便可,這種方式在生產環境中更加高效。
由於上述兩個狀況,有可能(雖然機率極小)最終多個nodes產生了相同的configEpoch;好比管理員正在進行resharding,可是此時failover發生了...不管是failover仍是resharding都是將currentEpoch自增,並且resharding不使用agreement形式(即其餘nodes或許不知道,並且網絡傳播可能延遲),這就會發生epoch值的衝突問題。
當持有不一樣slots的masters持有相同的configEpoch,這並不會有什麼問題。比較遺憾的是,人工干預或者resharding會以不一樣的方式修改了集羣的配置,Cluster要求全部的slots都應該被nodes覆蓋,因此在任何狀況下,咱們都但願全部的master都持有不一樣的configEpoch。避免衝突的算法,就是用來解決當2個nodes持有相同的configEpoch:
1)若是一個master節點發現其餘master持有相同的configEpoch。
2)而且此master邏輯上持有較小的node ID(字典順序)
3)而後此master將本身的currentEpoch加1,並做爲本身新的configEpoch。
若是有多個nodes持有相同的congfigEpoch,那麼除了持有最大ID的節點外,其餘的nodes都將往前推動(+1,直到衝突解決),最終保證每一個master都持有惟一的configEpoch(slave的configEpoch與master同樣)。對於新建立的cluster也是同理,全部的nodes都初始爲不一樣的configEpoch。
Node resets
全部的nodes均可以進行軟件級的reset(不須要重啓、從新部署它們),reset爲了重用集羣(從新設定集羣),必須須要將某個(些)節點重置後添加到其餘集羣。咱們可使用「CLUSTER RESET」指令:
1)CLUSTER RESET SOFT
2)CLUSTER RESET HARD
指令必須直接發給須要reset的節點,若是沒有指定reset類型,默認爲SOFT。
1)soft和hard:若是節點爲slave,那麼節點將會轉換爲master,並清空其持有的數據,成爲一個空的master。若是此節點爲master,且持有slots數據,那麼reset操做將被中斷。
2)soft和hard:其上持有的slots將會被釋放
3)soft和hard:此節點上的nodes映射表將會被清除,此後此node將不會知道其餘節點的存在與狀態。
4)hard:currentEpoch、configEpoch、lastVoteEpoch值將被重置爲0。
5)hard:此nodeID將會從新生成。
持有數據的(slot映射不爲空的)master不能被reset(除非現將此master上的slot手動遷移到其餘nodes上,或者手動failover,將其切換成slave);在某些特定的場景下,在執行reset以前,或許須要執行FLUSHALL來清空原有的數據。
集羣中移除節點
咱們已經知道,將node移除集羣以前,首先將其上的slots遷移到其餘nodes上(reshard),而後關閉它。不過這彷佛還並未結束,由於其餘nodes仍然記住了它的ID,仍然不會嘗試與它創建鏈接。所以,當咱們肯定將節點移除集羣時,可使用「CLUSTER FORGET <node-ID>」指令:
1)將此node從nodes映射表中移除。
2)而後設定一個60秒的隔離時間,阻止持有相同ID的node再次加入集羣。由於FORGET指令將會經過gossip協議傳播給其餘nodes,集羣中全部的節點都收到消息是須要必定的時間延遲。