不想談好吉他的擼鐵狗,不是好的程式設計師,歡迎微信關注「SH的全棧筆記」
前言
上文我們聊了基於Sentinel的Redis高可用架構,瞭解了Redis基於讀寫分離的主從架構,同時也知道當Redis的master發生故障之後,Sentinel叢集是如何執行failover的,以及其執行failover的原理是什麼。
這裡大概再提一下,Sentinel叢集會對Redis的主從架構中的Redis例項進行監控,一旦發現了master節點當機了,就會選舉出一個Sentinel節點來執行故障轉移,從原來的slave節點中選舉出一個,將其提升為master節點,然後讓其他的節點去複製新選舉出來的master節點。
你可能會覺得這樣沒有問題啊,甚至能夠滿足我們生產環境的使用需求了,那我們為什麼還需要Redis Cluster呢?
為什麼需要Redis Cluster
的確,在資料上,有replication副本做保證;可用性上,master當機會自動的執行failover。
那問題在哪兒呢?
首先Redis Sentinel說白了也是基於主從複製,在主從複製中slave的資料是完全來自於master。
假設master節點的記憶體只有4G,那slave節點所能儲存的資料上限也只能是4G。而且在之前的跟隨槓精的視角一起來了解Redis的主從複製文章中也說過,主從複製架構中是讀寫分離的,我們可以通過增加slave節點來擴充套件主從的讀併發能力,但是寫能力和儲存能力是無法進行擴充套件的,就只能是master節點能夠承載的上限。
所以,當你只需要儲存4G的資料時候的,基於主從複製和基於Sentinel的高可用架構是完全夠用的。
但是如果當你面臨的是海量的資料的時候呢?16G、64G、256G甚至1T呢?現在網際網路的業務裡面,如果你的體量足夠大,我覺得是肯定會面臨快取海量快取資料的場景的。
這就是為什麼我們需要引入Redis Cluster。
Redis Cluster是什麼
知道了為什麼需要Redis Cluster之後,我們就可以來對其一探究竟了。
那什麼是Redis Cluster呢?
很簡單,你就可以理解為n個主從架構組合在一起對外服務。Redis Cluster要求至少需要3個master才能組成一個叢集,同時每個master至少需要有一個slave節點。
這樣一來,如果一個主從能夠儲存32G的資料,如果這個叢集包含了兩個主從,則整個叢集就能夠儲存64G的資料。
我們知道,主從架構中,可以通過增加slave節點的方式來擴充套件讀請求的併發量,那Redis Cluster中是如何做的呢?雖然每個master下都掛載了一個slave節點,但是在Redis Cluster中的讀、寫請求其實都是在master上完成的。
slave節點只是充當了一個資料備份的角色,當master發生了當機,就會將對應的slave節點提拔為master,來重新對外提供服務。
節點負載均衡
知道了什麼是Redis Cluster,我們就可以繼續下面的討論了。
不知道你思考過一個問題沒,這麼多的master節點。我儲存的時候,到底該選擇哪個節點呢?一般這種負載均衡演算法,會選擇雜湊演算法。雜湊演算法是怎麼做的呢?
首先就是對key計算出一個hash值,然後用雜湊值對master數量進行取模。由此就可以將key負載均衡到每一個Redis節點上去。這就是簡單的雜湊演算法的實現。
那Redis Cluster是採取的上面的雜湊演算法嗎?答案是沒有。
Redis Cluster其實採取的是類似於一致性雜湊的演算法來實現節點選擇的。那為什麼不用雜湊演算法來進行例項選擇呢?以及為什麼說是類似的呢?我們繼續討論。
因為如果此時某一臺master發生了當機,那麼此時會導致Redis中所有的快取失效。為什麼是所有的?假設之前有3個master,那麼之前的演算法應該是 hash % 3,但是如果其中一臺master當機了,則演算法就會變成 hash % 2,會影響到之前儲存的所有的key。而這對快取後面保護的DB來說,是致命的打擊。
什麼是一致性雜湊
知道了通過傳統雜湊演算法來實現對節點的負載均衡的弊端,我們就需要進一步瞭解什麼是一致性雜湊。
我們上面提過雜湊演算法是對master例項數量來取模,而一致性雜湊則是對2^32取模,也就是值的範圍在[0, 2^32 -1]。一致性雜湊將其範圍抽象成了一個圓環,使用CRC16演算法計算出來的雜湊值會落到圓環上的某個地方。
然後我們的Redis例項也分佈在圓環上,我們在圓環上按照順時針的順序找到第一個Redis例項,這樣就完成了對key的節點分配。我們舉個例子。
假設我們有A、B、C三個Redis例項按照如圖所示的位置分佈在圓環上,此時計算出來的hash值,取模之後位置落在了位置D,那麼我們按照順時針的順序,就能夠找到我們這個key應該分配的Redis例項B。同理如果我們計算出來位置在E,那麼對應選擇的Redis的例項就是A。
即使這個時候Redis例項B掛了,也不會影響到例項A和C的快取。
例如此時節點B掛了,那之前計算出來在位置D的key,此時會按照順時針的順序,找到節點C。相當於自動的把原來節點B的流量給轉移到了節點C上去。而其他原本就在節點A和節點C的資料則完全不受影響。
這就是一致性雜湊,能夠在我們後續需要新增節點或者刪除節點的時候,不影響其他節點的正常執行。
虛擬節點機制
但是一致性雜湊也存在自身的小問題,例如當我們的Redis節點分佈如下時,就有問題了。
此時資料落在節點A上的概率明顯是大於其他兩個節點的,其次落在節點C上的概率最小。這樣一來會導致整個叢集的資料儲存不平衡,AB節點壓力較大,而C節點資源利用不充分。為了解決這個問題,一致性雜湊演算法引入了虛擬節點機制。
在圓環中,增加了對應節點的虛擬節點,然後完成了虛擬節點到真實節點的對映。假設現在計算得出了位置D,那麼按照順時針的順序,我們找到的第一個節點就是C #1,最終資料實際還是會落在節點C上。
通過增加虛擬節點的方式,使ABC三個節點在圓環上的位置更加均勻,平均了落在每一個節點上的概率。這樣一來就解決了上文提到的資料儲存存在不均勻的問題了,這就是一致性雜湊的虛擬節點機制。
Redis Cluster採用的什麼演算法
上面提到過,Redis Cluster採用的是類一致性雜湊演算法,之所以是類一致性雜湊演算法是因為它們實現的方式還略微有差別。
例如一致性雜湊是對2^32取模,而Redis Cluster則是對2^14(也就是16384)取模。Redis Cluster將自己分成了16384個Slot(槽位)。通過CRC16演算法計算出來的雜湊值會跟16384取模,取模之後得到的值就是對應的槽位,然後每個Redis節點都會負責處理一部分的槽位,就像下表這樣。
節點 | 處理槽位 |
---|---|
A | 0 - 5000 |
B | 5001 - 10000 |
C | 10001 - 16383 |
每個Redis例項會自己維護一份slot - Redis節點的對映關係,假設你在節點A上設定了某個key,但是這個key通過CRC16計算出來的槽位是由節點B維護的,那麼就會提示你需要去節點B上進行操作。
Redis Cluster如何做到高可用
不知道你思考過一個問題沒,如果Redis Cluster中的某個master節點掛了,它是如何保證叢集自身的高可用的?如果這個時候我們叢集需要擴容節點,它該負責哪些槽位呢?我們一個一個問題的來看一下。
叢集如何進行擴容
我們開篇聊過,Redis Cluster可以很方便的進行橫向擴容,那當新的節點加入進來的時候,它是如何獲取對應的slot的呢?
答案是通過reshard(重新分片)來實現。reshard可以將已經分配給某個節點的任意數量的slot遷移給另一個節點,在Redis內部是由redis-trib負責執行的。你可以理解為Redis其實已經封裝好了所有的命令,而redis-trib則負責向獲取slot的節點和被轉移slot的節點傳送命令來最終實現reshard。
假設我們需要向叢集中加入一個D節點,而此時叢集內已經有A、B、C三個節點了。
此時redis-trib會向A、B、C三個節點傳送遷移出槽位的請求,同時向D節點傳送準備匯入槽位的請求,做好準備之後A、B、C這三個源節點就開始執行遷移,將對應的slot所對應的鍵值對遷移至目標節點D。最後redis-trib會向叢集中所有主節點傳送槽位的變更資訊。
高可用及故障轉移
Redis Cluster中保證叢集高可用的思路和實現和Redis Sentinel如出一轍,感興趣的可以去看我之前寫的關於Sentinel的文章Redis Sentinel-深入淺出原理和實戰。
簡單來說,針對A節點,某一個節點認為A當機了,那麼此時是主觀當機。而如果叢集內超過半數的節點認為A掛了, 那麼此時A就會被標記為客觀當機。
一旦節點A被標記為了客觀當機,叢集就會開始執行故障轉移。其餘正常執行的master節點會進行投票選舉,從A節點的slave節點中選舉出一個,將其切換成新的master對外提供服務。當某個slave獲得了超過半數的master節點投票,就成功當選。
當選成功之後,新的master會執行slaveof no one
來讓自己停止複製A節點,使自己成為master。然後將A節點所負責處理的slot,全部轉移給自己,然後就會向叢集發PONG訊息來廣播自己的最新狀態。
按照一致性雜湊的思想,如果某個節點掛了,那麼就會沿著那個圓環,按照順時針的順序找到遇到的第一個Redis例項。
而對於Redis Cluster,某個key它其實並不關心它最終要去到哪個節點,他只關心他最終落到哪個slot上,無論你節點怎麼去遷移,最終還是隻需要找到對應的slot,然後再找到slot關聯的節點,最終就能夠找到最終的Redis例項了。
那這個PONG訊息又是什麼東西呢?別急,下面就會聊到。
簡單瞭解gossip協議
這就是Redis Cluster各個節點之間交換資料、通訊所採用的一種協議,叫做gossip。
gossip: 流言、八卦、小道訊息
gossip是在1989年的論文上提出的,我看了一堆資料都說的是1987年發表的,但是文章裡的時間明確是1989年1月份發表。
感興趣的可以去看看Epidemic Algorithms for Replicated . Database Maintenance,在當時提出gossip主要是為了解決在分散式資料庫中,各個副本節點的資料同步問題。但隨著技術的發展,gossip後續也被廣泛運用於資訊擴散、故障探測等等。
Redis Cluster就是利用了gossip來實現自身的資訊擴散的。那使用gossip具體是如何通訊的呢?
很簡單,就像圖裡這樣。每個Redis節點每秒鐘都會向其他的節點傳送PING,然後被PING的節點會回一個PONG。
gossip協議訊息型別
Redis Cluster中,節點之間的訊息型別有5種,分別是MEET、PING、PONG、FAIL和PUBLISH。這些訊息分別傳遞了什麼內容呢?我簡單總結了一下。
訊息型別 | 訊息內容 |
---|---|
MEET | 給某個節點傳送MEET訊息,請求接收訊息的節點加入到叢集中 |
PING | 每隔一秒鐘,選擇5個最久沒有通訊的節點,傳送PING訊息,檢測對應的節點是否線上;同時還有一種策略是,如果某個節點的通訊延遲大於了cluster-node-time 的值的一半,就會立即給該節點傳送PING訊息,避免資料交換延遲過久 |
PONG | 當節點接收到MEET或者PING訊息之後,會回一個PONG訊息給傳送方,代表自己收到了MEET或者PING訊息。同時,節點也可以主動的通過PONG訊息向叢集中廣播自己的資訊,讓其他節點獲取到自己最新的屬性,就像完成了故障轉移之後新的master向叢集傳送PONG訊息一樣 |
FAIL | 用於廣播自己的對某個節點的當機判斷,假設當前節點對A節點判斷為當機,就會立即向Redis Cluster廣播自己對於A節點的判斷,所有收到訊息的節點就會對A節點做標記 |
PUBLISH | 用於向指定的Channel傳送訊息,某個節點收到PUBLISH訊息之後會直接在叢集內廣播,這樣一來,客戶端無論連線到任何節點都能夠訂閱這個Channel |
使用gossip的優劣
既然Redis Cluster選擇了gossip,那肯定存在一些gossip的優點,我們接下來簡單梳理一下。
優點 | 描述 |
---|---|
擴充套件性 | 網路可以允許節點的任意增加和減少,新增加的節點的狀態最終會與其他節點一致。 |
容錯性 | 由於每個節點都持有一份完整後設資料,所以任何節點當機都不會影響gossip的執行 |
健壯性 | 與容錯性類似,由於所有節點都持有資料,地位平臺,是一個去中心化的設計,任何節點都不會影響到服務的執行 |
最終一致性 | 當有新的資訊需要傳遞時,訊息可以快速的傳送到所有的節點,讓所有的節點都擁有最新的資料 |
gossip可以在O(logN) 輪就可以將資訊傳播到所有的節點,為什麼是O(logN)呢?因為每次ping,當前節點會帶上自己的資訊外加整個Cluster的1/10數量的節點資訊,一起傳送出去。你可以簡單的把這個模型抽象為:
你轉發了一個特別有意思的文章到朋友圈,然後你的朋友們都覺得還不錯,於是就一傳十、十傳百這樣的散播出去了,這就是朋友圈的裂變傳播。
當然,gossip仍然存在一些缺點。例如訊息可能最終會經過很多輪才能到達目標節點,而這可能會帶來較大的延遲。同時由於節點會隨機選出5個最久沒有通訊的節點,這可能會造成某一個節點同時收到n個重複的訊息。
總結
總的來說,Redis Cluster相當於是把Redis的主從架構和Sentinel整合到了一起,從Redis Cluster的高可用機制、判斷故障轉移以及執行故障轉移的過程,都和主從、Sentinel相關,這也是為什麼我在之前的文章裡說,主從是Redis高可用架構的基石。
好了以上就是本篇部落格的全部內容了,如果你覺得這篇文章對你有幫助,還麻煩點個贊,關個注,分個享,留個言。
歡迎微信搜尋關注【SH的全棧筆記】,檢視更多相關文章
- END -推薦閱讀: