Redis Cluster

Redis3.0之後,節點之間經過去中心化的方式提供了完整的sharding(數據分片)、replication(複製機制、Cluster具有感知準備的能力)、failover解決方案node

拓撲結構

Redis Cluster由多個Redis節點組構成。不一樣節點組服務的數據無交集,每個節點組對應數據sharding的一個分片。算法

節點組內分爲主備兩類節點,二者數據準實時一致,經過異步化的主備複製機制緩存

master節點對用戶提供讀寫服務,slave節點對用戶提供讀服務。服務器

F59oCQ.png

Redis Cluster總共有16384個slot,每個節點負責一部分slot。網絡

Redis Cluster中全部的幾點之間兩兩經過Redis Cluster Bus交互,主要交互如下關鍵信息:數據結構

  • 數據分片(slot)和節點的對應關係
  • 集羣中每一個節點可用狀態
  • 集羣結構發生變動時,經過必定的協議對配置信息達成一致。數據分片的遷移、故障發生時的主備切換決策、單點master的發現和其發生主備關係的變動等場景均會致使集羣結構變化
  • publish和subscribe(發佈/訂閱)功能在cluster版的內容實現所須要交互的信息。

Redis Cluster Bus經過單獨的端口進行鏈接,bus是節點間的內部通訊機制,交互的是字節序列化信息,而不是client到Redis服務器的字符序列化以提高交互效率。架構

Redis Cluster是去中心化的分佈式實現方案,客戶端能夠和集羣中的任一節點鏈接。併發

配置一致性

去中心化意味着集羣的拓撲結構並不保存在單獨的配置節點上,Redis Cluster經過引入兩個自增的epoch變量來使得集羣配置在各個節點間達成最終一致。異步

配置信息的數據結構

Redis Cluster中的每個節點都保存了集羣的配置信息,這些信息存儲在clusterState中。分佈式

  • clusterState記錄了從集羣中某個節點的視角看來的集羣配置狀態
  • currentEpoch表示整個集羣中的最大版本號,集羣信息每變動一次,該版本號都會自增以保證每一個信息的版本號惟一
  • nodes是一個列表,包含了本節點所知的集羣全部節點的信息(clusterNode),其中也包含本節點自身
  • clusterNode記錄了每一個節點的信息,比較關鍵的信息包括該信息的版本epoch,該版本信息的描述:該節點對應的數據分片(slot),當該節點爲master節點時對應的slave節點列表、當該節點爲slave時對應的master節點
  • 每一個clusterNode還包含了一個全局惟一的nodeId
  • 當集羣的數據分片信息發生變動時,Redis Cluster仍然保持對外服務,在遷移過程當中,經過分片遷移相關狀態的一組變量來管控遷移過程
  • 當集羣中的某個master出現宕機時,Redis Cluster會自動發現並觸發故障轉移的操做,將宕機master的某個slave升級爲master,這個過程一樣須要一組failover相關狀態的變量來管控故障轉移。

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變量將本身的最新信息擴散到整個集羣,達到最終一致。

  • 配置信息clusterNode的epoch屬性描述的粒度是單個節點
  • 配置信息clusterState的currentEpoch屬性的粒度是整個集羣,它的存在用來輔助epoch自增的生成。因爲currentEpoch信息也是維護在各個幾點自身的,Redis Cluster結構在發生變動時,經過必定時間窗口控制和更新規則保證每一個節點看到的currentEpoch都是最新的。

集羣信息的更新規則:

  • 當某個節點率先知道了信息變動時,這個節點將currentEpoch自增使之成爲集羣中的最大值,再用自增後的currentEpoch做爲新的epoch版本
  • 當某個節點收到了比本身大的currentEpoch時,更新本身的currentEpoch值使之保持最新
  • 當收到的Redis Cluster Bus消息中某個節點信息的epoch值大於接收者本身內部的配置信息存儲的值時,意味着本身的信息太舊,此時接收者直接將本身的映射信息更新爲消息的內容
  • 當收到的Redis Cluster Bus消息中某個節點信息未包含在接收節點的內部配置信息中時,意味着接受者還沒有意識到該節點的存在,此時接收者直接將消息的信息添加到本身的內部配置信息中。

sharding

不一樣的節點組服務於相互無交互的數據子集(sharding,分片)。

數據分片(slot)

Redis Cluster將全部的數據劃分爲16384個分片(slot),每一個分片負責其中一部分。每一條數據根據key值經過數據分佈算法映射到16384個slot中的一個。

數據分佈算法:slotId=crc(key)%16384

客戶端根據slotId決定將請求路由到哪一個Redis節點。Cluster不支持跨節點的單命令

爲此,Redis引入HashTag的概念,使得數據分佈算法能夠根據key的某一部分進行計算,讓相關的兩條記錄落到同一個數據分片,例如:

  • 某條商品交易記錄的key值爲:product_trade_{prod123}
  • 這個商品的詳情記錄的key值爲:product_detail_{prod123}

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對應的節點是肯定的。可是在某些狀況下,節點和分片的對應關係要發生變動:

  • 新的節點做爲master加入
  • 某個節點分組須要下線
  • 負載不均須要調整slot分佈

此時須要進行分片的遷移。分片遷移的觸發和過程由外部系統完成,Redis Cluster只提供遷移過程當中須要的原語供外部系統調用。這些原語主要有兩種:

  • 節點遷移狀態設置:遷移前標記源/目標節點
  • key遷移的原子化命令:遷移的具體步驟

F5PDld.png

  1. 向節點B發送狀態變動命令,將B的對應slot狀態置爲IMPORTING
  2. 向節點A發送狀態變動命令,將A的對應slot狀態置爲MIGRATING
  3. 針對A的slot上的全部的key,分別向A發送MIGRATE命令,告知A將對應key的數據遷移到B。

當節點A的狀態被設置爲了MIGRATING後,表示對應的slot正在從A遷出,爲保證該slot數據的一致性,A此時對slot內部數據提供讀寫服務的行爲和一般狀態下有所區別,對於某個遷移中的slot:

  • 若是客戶端訪問的key還沒有遷移出,則正常地處理key
  • 若是key已經被遷移出或者根本不存在該key,則回覆客戶端ASK信息讓其跳轉到B執行

當節點B的狀態被設置爲了IMPORTING以後,表示對應的slot正在向B遷入中,即便B仍能對外提供該slot的讀寫服務,但行爲和一般狀態下也有所區別:

  • 當來自客戶端的正常訪問不是從ASK跳轉而來時,說明客戶端尚不知道遷移正在進行,頗有可能操做了一個目前還沒有遷移完成的正處在A上的key,若是此時key已經在A上被修改了,那麼B和A的修改值將在將來發生衝突。
  • 對於該slot上的全部非ASK跳轉而來的操做,B不會進行處理,而是經過MOVED命令讓客戶端跳轉至A執行

這樣的狀態控制能夠保證同一個key在遷移以前老是在源節點執行,遷移後老是在目標節點執行,杜絕了兩邊同時寫致使值衝突的可能性。且遷移過程當中新增的key老是在目標節點執行,源節點不會再有新增的key,使得遷移過程時間有界。

Redis單機對於命令的處理是單線程的,同一個key在MIGRATE的過程當中不會處理對該key的其餘操做,從而保證了遷移的原子性。

當slot的全部key從A遷移至B上以後,客戶端經過CLUSTER SETSLOT命令設置B的分片信息,使之包含遷移的slot。設置的過程當中會自增一個epoch,它大於當前集羣中的全部epoch值,這個新的配置信息會傳播到集羣中的其餘每個節點,完成分片節點映射關係的更新。

failover

Redis Cluster同Sentinel同樣,具有完整的節點故障發現、故障狀態一致性保證、主備切換機制。

failover狀態變遷

failover的過程以下:

  1. 故障發現:當某個master宕機時,宕機事件如何被集羣其餘節點感知
  2. 故障確認:多個節點就某個master是否宕機如何達成一致
  3. slave選舉:集羣確認了某個master確實宕機後,如何將它的slave升級成新的master;若是原master有多個slave,選擇誰升級
  4. 集羣結構變動:選舉成功的slave升級成新的master後如何讓全集羣的其餘節點知道以更新他們的集羣結構信息

故障發現

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選舉流程

Ff26Nd.png

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主動觸發

F4kyKs.png

  1. slave向master發起SYNC命令。這一步在slave啓動後觸發,master被動地將新進slave節點加入本身的主備複製集羣
  2. master收到SYNC後,開啓BGSAVE操做。BGSAVE是Redis的一種全量模式的持久化機制
  3. BGSAVE完成後,master會將快照信息發送給slave
  4. 發送期間,master收到的來自客戶端的新的寫命令,除了正常響應外,都再存入一份到backlog隊列
  5. 快照信息發送完成後,master繼續發送backlog隊列信息
  6. backlog發送完成後,後續的寫操做同時發送給slave,保持實時地異步複製

slave側的處理邏輯:

  1. 發送完SYNC後,繼續對外提供服務
  2. 開始接收master的快照信息,此時,將slave現有數據清空,並將master快照寫入自身內存
  3. 接收backlog內容並執行它,即回放,期間對外提供讀請求
  4. 繼續接收後續來自master的命令副本並繼續回放,以保證數據和master一致

若是有多個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,而斷開前已經同步過的數據,則再也不從新同步,這樣減小了數據傳輸開銷

可用性和性能

Redis Cluster讀寫分離

對於有讀寫分離需求的場景,應用對於某些讀的請求容許捨棄必定的數據一致性,以換取更高的讀吞吐量,此時但願將讀的請求交由slave處理以分擔master的壓力

默認狀況下,數據分片映射關係中,某個slot對應的節點必定是一個master節點,客戶端經過MOVED消息得知的集羣拓撲結構也只會將請求路由到各個master中,即使客戶將讀請求直接發送到slave上,後者也會回覆MOVED到master的響應。

Redis Cluster引入了READONLY命令。客戶端向slave發送該命令後,slave對於讀操做,將再也不MOVED回master而不是直接處理,這被稱爲slave的READONLY模式。經過READWRITE命令,可將slave的READONLY模式重置。

master單點保護

集羣只須要保持2*master+1個節點,就能夠在任一節點宕機後仍然自動地維持,稱爲master的單點保護。

相關文章
相關標籤/搜索