#作者:stackofumbrella
文章目錄
- Redis Cluster特點
- Redis Cluster與其它集群模式的區別
- 集群目標
- 性能
- hash tags
- Mutli-key操作
- Cluster Bus
- 安全寫入(write safety)
- 集群節點的屬性
- 集群拓撲
- 節點間handshake
- 重定向與resharding
- MOVED重定向
- ASK重定向
- 客戶端首次鏈接以及重定向處理
Redis Cluster特點
多主多從,去中心化:從節點作為備用,復制主節點,不做讀寫操作,不提供服務
不支持處理多個key:因為數據分散在多個節點,在數據量大高并發的情況下會影響性能
支持動態擴容節點:這算是Redis Cluster最大的優點之一
節點之間相互通信,相互選舉,不再依賴sentinel:準確來說是主節點之間相互監督,保證及時故障轉移
Redis Cluster與其它集群模式的區別
相比較sentinel模式,多個master節點保證主要業務(比如master節點主要負責寫)穩定性,不需要搭建多個sentinel實例監控一個master節點
,相比較一主多從的模式,具有自我故障檢測,故障轉移的特點
,相比較其他兩個模式而言,對數據進行分片(sharding),不同節點存儲的數據是不一樣的。從某種程度上來說,Sentinel模式主要針對高可用(HA),而Cluster模式是不僅針對大數據量,高并發,同時也支持HA。
Redis Cluster的實現機制和原理
集群目標
1)高性能和線性擴展,最大可以支撐到1000個節點,Cluster架構中無Proxy層,Master與slave之間使用異步replication,且不存在操作的merge(即操作不能跨多個nodes,不存在merge層)。
2)一定程度上保證writes的安全性,需要客戶端容忍一定程度的數據丟失。集群將會盡可能(best-effort)保存客戶端write操作的數據,通常在failover期間,會有短暫時間內的數據丟失(因為異步replication引起),當客戶端與少數節點處于網絡分區時(network partition),丟失數據的可能性會更高,因節點有效性檢測,failover需要更長的時間。
3)只要集群中大多數master可達、且失效的master至少有一個slave可達時,集群都可以繼續提供服務。同時replicas migration可以將那些擁有多個slaves的master的某個slave,遷移到沒有slave的master下,即將slaves的分布在整個集群相對平衡,盡力確保每個master都有一定數量的slave備份。Redis Cluster集群由多個shard組成,每個shard可以有一個master和多個slaves構成,數據根據hash slots配額分布在多個shard節點上,節點之間建立雙向TCP鏈接用于有效性檢測、Failover等,Client直接與shard節點進行通訊,集群暫不提供動態reblance策略。
性能
Redis Cluster并沒有提供Proxy層,而是告知客戶端將key的請求轉發給合適的node。Client保存集群中nodes與keys的映射關系(slots),并保持此數據的更新,所以通常Client總能夠將請求直接發送到正確的node上。因為采用異步replication,所以master不會等待slaves保存成功后才向客戶端反饋結果,除非顯式的指定了WAIT指令。multi-key指令僅限于單個節點內,除了resharding操作外,節點的數據不會在節點間遷移。每個操作只會在特定的一個節點上執行,所以集群的性能為master節點的線性擴展。同時Clients與每個node保持鏈接,所以請求的延遲等同于單個節點,即請求的延遲并不會因為Cluster的規模增大而受到影響。高性能和擴展性,同時保持合理的數據安全性,是Redis Cluster的設計目標。
hash tags
在計算hash slots時有一個意外的情況,用于支持“hash tags”,hash tags用于確保多個keys能夠被分配在同一個hash slot中,每個key都可以包含一個自定義的“tags”,那么在存儲時將根據tags計算此key應該分布在哪個node上(而不是使用key計算,但是存儲層面仍然是key)。此特性可以強制某些keys被保存在同一個節點上,用于支持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”。
Mutli-key操作
Redis單實例支持的命令,Cluster也都支持,但是對于“multi-key”操作(即一次RPC調用需要進行多個key的操作)比如Set類型的交集、并集等,則要求這些key必須屬于同一個node。Cluster不能進行跨Node操作,也沒有node提供merge層代理。在人工對slots進行resharding期間,multi-key操作可能不可用。比如這些keys不存在于同一個slot(遷移會導致keys被分離)。比如Multikeys邏輯上屬于同一個slot,但是因為resharding,它們可能暫時不處于同一個node,有些可能在遷移的目標節點上(比如Multikeys包含a、b、c三個keys,邏輯上它們都屬于slot 8,但是其中c在遷移期間創建,它被存儲在節點B上,a、b仍然在節點A),此時將會向客戶端返回“-
TRYAGAIN”錯誤,那么客戶端此后將需要重試一次,或者直接返回錯誤(如果遷移操作被中斷),無論如何最終Multikeys的訪問邏輯是一致的,slots的狀態也是最終確定的。
Cluster Bus
集群中node負責存儲數據,保持集群的狀態,包括keys與node的對應關系(內部其實為slots與node對應關系)。node也能夠自動發現其他的node,檢測失效的節點,當某個master失效時還能將合適的slave提升為master。為了達成這些行為,集群中的每個節點都通過TCP與其他所有node建立連接,它們之間的通信協議和方式稱為“Redis Cluster Bus”。 每個Node都有一個特定的TCP端口,用來接收其他nodes的鏈接,此端口號為面向Client的端口號+10000,比如客戶端端口號為6379,那么node的Bus端口號為16379,客戶端端口號可以在配置文件中聲明。由此可見,node之間的交互通訊是通過Bus端口進行,使用gossip協議向其他node傳播集群信息,以達到自動發現的特性,通過發送ping來確認其他node工作正常,也會在合適的時機發送集群信息。當然在Failover時(包括人為failover)也會使用Bus來傳播消息。
gossip最終一致性,分布式服務數據同步算法,node首先需要知道(可以讀取配置)集群中至少一個seed node,此node向seed發送ping請求,此時seed節點pong返回自己已知的所有node列表,然后node解析列表并與它們都建立tcp連接,同時也會向每個node發送ping,并從它們的pong結果中merge出全局node列表,并逐步與所有的node建立連接,數據傳輸的方式也是類似,網絡拓撲結構為full mesh。
安全寫入(write safety)
在Master-slaves之間使用異步replication機制,在failover之后,新的Master將會最終替代舊的master。在出現網絡分區時(network partition),總會有個窗口期(node timeout)可能會導致數據丟失。不過,Client與多數派Master、少數派Master處于一個分區(網絡分區,因為網絡阻斷問題,導致Clients與Nodes被隔離成兩部分)時,這兩種情況下影響并不相同。
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操作將丟失。
集群節點的屬性
集群中每個節點都有唯一的名字,稱之為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”指令開查看。
集群拓撲
Redis Cluster中每個node都與其他node的Bus端口建立TCP鏈接(full mesh,全網)。比如在有N個節點的集群中,每個node有N-1個向外發出的TCP鏈接,以及N-1個其他node發過來的TCP鏈接。這些TCP鏈接總是keepalive,不是按需創建的。如果ping發出之后,node在足夠長的時間內仍然沒有pong響應,那么此node將會被標記為“不可達”,那么與此node的鏈接將會被刷新或者重建。Nodes之間通過gossip協議和配置更新的機制,來避免每次都交互大量的消息,最終確保在nodes之間的信息傳送量是可控的。
節點間handshake
Nodes通過Bus端口發送ping、pong,如果一個節點不屬于集群,那么它的消息將會被其他node全部丟棄。一個節點被認為是集群成員的方式有2種:
1)如果此node在“Cluster meet”指令中引入,此命令的主要意義就是將指定node加入集群。那么對于當前節點,將認為指定的node為“可信任的”(此后將會通過gossip協議傳播給其他node)。
2)當其他node通過gossip引入了新的node,這些node也是被認為是“可信任的”。
只要將一個節點加入集群,最終此節點將會與其他節點建立鏈接,即cluster可以通過信息交換來自動發現新的節點,鏈接拓撲仍然是full mesh。
重定向與resharding
MOVED重定向
因為redis并不提供Proxy機制,當Client將請求發給錯誤的node時(此node上不存在此key所屬的slot),node將會反饋“MOVED”或“ASK”錯誤信息,以便Client重新定向到合適的node。理論上,Client可以將請求隨意發給任何一個node,包括slaves,此node解析query,如果可以執行(比如語法正確,multiple keys都應該在一個node slots上),它會查看key應該屬于哪個slot、以及此slot所在的node,如果當前node持有此slot,那么query直接執行即可,否則當前node將會向Client反饋“MOVED”錯誤。錯誤信息中包括此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刷新本地的集群信息。
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映射關系(即node與slots的關系),每次請求都從聲明的、已知的node中,隨機訪問一個node,并根據重定向(MOVED)信息來尋找合適的node,這種訪問模式,通常是非常低效的。當然,Client應該盡可能的將slots配置信息緩存在本地,不過配置信息也不需要絕對的實時更新,因為在請求時偶爾出現“重定向”,Client也能兼容此次請求的正確轉發,此時再更新slots配置,所以Client通常不需要間歇性的檢測Cluster中配置信息是否已經更新,客戶端通常是全量更新slots配置:
1)首次鏈接到集群的某個節點;
2)當遇到MOVED重定向消息時。
遇到MOVED時,客戶端僅僅更新特定的slot是不夠的,因為集群中的reshard通常會影響到多個slots。客戶端通過向任意一個node發送“CLUSTER NODES”或“CLUSTER SLOTS”指令均可以獲得當前集群最新的slots映射信息,“CLUSTER SLOTS”指令返回的信息更易于Client解析。如果集群處于broken狀態,即某些slots尚未被任何nodes覆蓋,指令返回的結果可能是不完整的。