Redis3.0之後,節點之間經過去中心化的方式提供了完整的sharding(數據分片)、replication(複製機制、Cluster具有感知準備的能力)、failover解決方案。node
Redis Cluster由多個Redis節點組構成。不一樣節點組服務的數據無交集,每個節點組對應數據sharding的一個分片。算法
節點組內分爲主備兩類節點,二者數據準實時一致,經過異步化的主備複製機制。緩存
master節點對用戶提供讀寫服務,slave節點對用戶提供讀服務。服務器
Redis Cluster總共有16384個slot,每個節點負責一部分slot。網絡
Redis Cluster中全部的幾點之間兩兩經過Redis Cluster Bus交互,主要交互如下關鍵信息:數據結構
Redis Cluster Bus經過單獨的端口進行鏈接,bus是節點間的內部通訊機制,交互的是字節序列化信息,而不是client到Redis服務器的字符序列化以提高交互效率。架構
Redis Cluster是去中心化的分佈式實現方案,客戶端能夠和集羣中的任一節點鏈接。併發
去中心化意味着集羣的拓撲結構並不保存在單獨的配置節點上,Redis Cluster經過引入兩個自增的epoch變量來使得集羣配置在各個節點間達成最終一致。異步
Redis Cluster中的每個節點都保存了集羣的配置信息,這些信息存儲在clusterState中。分佈式
Redis Cluster經過epoch做爲版本號來實現集羣配置的一致性。
去中心化的架構不存在統一的配置中心,各個節點對集羣狀態的認知來自於節點間的信息交互。在Redis Cluster中,該信息的交互經過Redis Cluster Bus來完成。
clusterMsg的type字段指明瞭消息的類型。配置信息的一致性主要依靠PING和PONG,二者除了type不一樣,其他字段語義均相同,消息體爲Gossip數據。
每個節點向其餘節點較爲頻繁的週期性發送PING消息和接受PONG響應。在這些下拍戲的Gossip部分,包含了發送者節點(或者響應者節點)所知的集羣其餘節點信息,接收節點能夠根據這些Gossip信息更新本身對於集羣的認知。
規模較大的集羣可能存在上千個節點,可是這些節點在正常狀況下都是穩定的,所以每次都發送全量數據並沒必要要,並且還會形成網絡負擔。
做爲優化,Redis Cluster在每次的PING和PONG包中,只包含全集羣部分節點信息,節點隨機選取,以此控制網絡流量。因爲交互頻繁,短期的幾回交互以後,集羣狀態就會以Gossip協議的方式被擴散到了集羣中的全部節點。
集羣結構穩定不發生變化時,各個節點經過Gossip協議在幾輪交互以後即可得知全集羣的信息而且達到一致的狀態。
可是,當發生故障轉移、分片遷移等狀況將會形成集羣結構變動,變動的信息須要各個節點之間自行協調,優先得知變動信息的節點利用epoch變量將本身的最新信息擴散到整個集羣,達到最終一致。
集羣信息的更新規則:
不一樣的節點組服務於相互無交互的數據子集(sharding,分片)。
Redis Cluster將全部的數據劃分爲16384個分片(slot),每一個分片負責其中一部分。每一條數據根據key值經過數據分佈算法映射到16384個slot中的一個。
數據分佈算法:slotId=crc(key)%16384
客戶端根據slotId決定將請求路由到哪一個Redis節點。Cluster不支持跨節點的單命令。
爲此,Redis引入HashTag的概念,使得數據分佈算法能夠根據key的某一部分進行計算,讓相關的兩條記錄落到同一個數據分片,例如:
Redis會根據{}之間的子字符串做爲數據分佈算法的輸入。
Redis Cluster的客戶端須要具有必定的路由能力。當一個Client訪問的key不在對應Redis節點的slot中,Redis返回給Client一個moved命令,告知其正確的路由信息。
從Client收到moved響應,到再次向moved響應中指向的節點發送請求期間,Redis Cluster的數據分佈可能又發生了變動,此時,指向的節點會繼續響應moved。Client根據moved響應更新其內部的路由緩存信息,以便下一次請求時直接路由到正確的節點,下降交互次數。
當Cluster處在數據重分佈(目前由人工觸發)過程當中時,能夠經過ask命令控制客戶端路由。
ask命令和moved命令的不一樣語義在於,後者會更新路由緩存,前者只是本條操做重定向到新節點,後續的相同slot操做仍路由到舊節點。ask類型將重定向和路由緩存更新分離,避免客戶端的路由緩存信息頻繁更新。
在穩定的Redis Cluster下,每個slot對應的節點是肯定的。可是在某些狀況下,節點和分片的對應關係要發生變動:
此時須要進行分片的遷移。分片遷移的觸發和過程由外部系統完成,Redis Cluster只提供遷移過程當中須要的原語供外部系統調用。這些原語主要有兩種:
當節點A的狀態被設置爲了MIGRATING後,表示對應的slot正在從A遷出,爲保證該slot數據的一致性,A此時對slot內部數據提供讀寫服務的行爲和一般狀態下有所區別,對於某個遷移中的slot:
當節點B的狀態被設置爲了IMPORTING以後,表示對應的slot正在向B遷入中,即便B仍能對外提供該slot的讀寫服務,但行爲和一般狀態下也有所區別:
這樣的狀態控制能夠保證同一個key在遷移以前老是在源節點執行,遷移後老是在目標節點執行,杜絕了兩邊同時寫致使值衝突的可能性。且遷移過程當中新增的key老是在目標節點執行,源節點不會再有新增的key,使得遷移過程時間有界。
Redis單機對於命令的處理是單線程的,同一個key在MIGRATE的過程當中不會處理對該key的其餘操做,從而保證了遷移的原子性。
當slot的全部key從A遷移至B上以後,客戶端經過CLUSTER SETSLOT命令設置B的分片信息,使之包含遷移的slot。設置的過程當中會自增一個epoch,它大於當前集羣中的全部epoch值,這個新的配置信息會傳播到集羣中的其餘每個節點,完成分片節點映射關係的更新。
Redis Cluster同Sentinel同樣,具有完整的節點故障發現、故障狀態一致性保證、主備切換機制。
failover的過程以下:
Redis Cluster節點間經過Redis Cluster Bus兩兩週期性地進行PING/PONG交互,當某個節點宕機時,其餘發向它的PING消息將沒法及時響應,當PONG的響應超過必定時間(NODE_TIMEOUT)未收到,則發送者認爲接受節點故障,將其置爲PFAIL狀態,後續經過Gossip發出的PING/PONG消息中,這個節點的PFAIL狀態將會被轉播到集羣的其餘節點。
Redis Cluster的節點間經過TCP保持Redis Cluster Bus鏈接,當對端無PONG回覆時,除了節點故障外,還有多是TCP鏈接斷開。對於TCP鏈接斷開致使的響應超時,將會產生節點狀態誤報。所以Redis Cluster經過預重試機制排除此類誤報:當NODE_TIMEOUT/2過去了卻還未收到PING對應的PONG消息,則重建鏈接重發PING消息,若是對端正常,PONG會在很短期內抵達。
對於網絡分割的節點,某個節點(假設叫B節點)並無故障,但可能和A沒法鏈接,可是和C/D等其餘節點能夠正常聯通,此時只有A會將B標記爲PFAIL,其餘節點扔人認爲B是正常的。此時A和C/D等其餘節點信息不一致。Redis Cluster經過故障確認協議達成一致。
A會受到來自其餘節點的Gossip消息,被告知節點B是否處於PFAIL狀態,當A受到的來自其餘master節點的B的PFAIL達到必定數量後,會將B的PFAIL升級爲FAIL狀態,表示B已確認爲故障,後續將會發起slave選舉流程
上例中,若是B是A的master,且B已經被集羣公認是FAIL狀態,那麼A將發起競選,指望替代B成爲新的master。
若是B有多個slave A/E/F都意識到B處於FAIL狀態了,A/E/F可能會同時發起競選,當B的slave數量>=3個時,頗有可能由於票數均勻沒法選出勝者,延長B上的slot不可用時間。爲此,slave間會在選舉前協商優先級,優先級高的slave更有可能早地發起選舉,優先級較低的slave發起選舉的時間越靠後,避免和高優先級的slave競爭,提高一輪完成選舉的可能性。
優先級最重要的決定因素是slave最後一次同步master信息的時間,越新標識這個slave的數據越新,競選優先級越高。
slave經過向其餘master節點發送FAILOVER_AUTH_REQUEST消息發起競選,master收到以後回覆FAILOVER_AUTH_ACK消息告知本身是否贊成改slave成爲新的master。slave發送FAILOVER_AUTH_REQUEST前會將currentEpoch自增並將最新的epoch帶入到AILOVER_AUTH_REQUEST消息中,master收到FAILOVER_AUTH_REQUEST消息後,若是發現對於本輪(本epoch)本身還沒有投過票,則回覆贊成,不然回覆拒絕。
當slave收到超過半數的master的贊成回覆時,該slave順利的替代B成爲新master,此時它會以最新的epoch經過PONG消息廣播本身成爲master的信息,讓集羣中的其餘節點更快地更新拓撲信息。
當B恢復可用以後,它首先仍然認爲本身是master,但逐漸得經過Gossip協議得知A已經替代本身的事實以後降級爲A的slave。
Redis採用主備複製的方式保持一致性,即全部節點中,有一個節點爲master,對外提供寫入服務,全部的數據變動由外界對master的寫入觸發,以後Redis內部異步地將數據從主節點複製到其餘節點上。
Redis包含master和slave節點:master節點對外提供讀寫服務;slave節點做爲master的數據備份,擁有master的全量數據,對外不提供寫服務。主備複製由slave主動觸發。
slave側的處理邏輯:
若是有多個slave節點併發發送SYNC命令給master,只要第二個slave的SYNC命令發生在master完成BGSAVE以前,第二個slave將受到和第一個slave相同的快照和後續的backlog;不然,第二個slave的SYNC將觸發master的第二次BGSAVE。
slave經過SYNC命令和master進行數據同步時,master都會dump全量數據。假設master和slave斷開很短的時間,數據只有不多的差別,重連後也會發送這些全量數據致使大量的無效開銷。最好的方式就是,master-slave只同步斷開期間的少許數據。
Redis的PSYNC可用於替代SYNC,作到master-slave基於斷點續傳的主備同步協議。master-slave兩端經過維護一個offset記錄當前已經同步過的命令,slave斷開期間,master的客戶端命令會保持在緩存中,在slave命令重連後,告知master斷開時的最新offset,master則將緩存中大於offset的數據發送給slave,而斷開前已經同步過的數據,則再也不從新同步,這樣減小了數據傳輸開銷。
對於有讀寫分離需求的場景,應用對於某些讀的請求容許捨棄必定的數據一致性,以換取更高的讀吞吐量,此時但願將讀的請求交由slave處理以分擔master的壓力。
默認狀況下,數據分片映射關係中,某個slot對應的節點必定是一個master節點,客戶端經過MOVED消息得知的集羣拓撲結構也只會將請求路由到各個master中,即使客戶將讀請求直接發送到slave上,後者也會回覆MOVED到master的響應。
Redis Cluster引入了READONLY命令。客戶端向slave發送該命令後,slave對於讀操做,將再也不MOVED回master而不是直接處理,這被稱爲slave的READONLY模式。經過READWRITE命令,可將slave的READONLY模式重置。
集羣只須要保持2*master+1個節點,就能夠在任一節點宕機後仍然自動地維持,稱爲master的單點保護。