1. 程式人生 > >淺談叢集版Redis和Gossip協議

淺談叢集版Redis和Gossip協議

昨天的文章寫了關於分散式系統中一致性雜湊演算法的問題,文末提了一下Redis-Cluster對於一致性雜湊演算法的實現方案,今天來看一下Redis-Cluster和其中的重要概念Gossip協議。

1.Redis Cluster的基本概念

叢集版的Redis聽起來很高大上,確實相比單例項一主一從或者一主多從模式來說複雜了許多,網際網路的架構總是隨著業務的發展不斷演進的。

  • 單例項Redis架構

最開始的一主N從加上讀寫分離,Redis作為快取單例項貌似也還不錯,並且有Sentinel哨兵機制,可以實現主從故障遷移。

單例項一主兩從+讀寫分離結構:

注:圖片來自網路

單例項的由於本質上只有一臺Master作為儲存,就算機器為128GB的記憶體,一般建議使用率也不要超過70%-80%,所以最多使用100GB資料就已經很多了,實際中50%就不錯了,以為資料量太大也會降低服務的穩定性,因為資料量太大意味著持久化成本高,可能嚴重阻塞服務,甚至最終切主。

如果單例項只作為快取使用,那麼除了在服務故障或者阻塞時會出現快取擊穿問題,可能會有很多請求一起搞死MySQL。

如果單例項作為主存,那麼問題就比較大了,因為涉及到持久化問題,無論是bgsave還是aof都會造成刷盤阻塞,此時造成服務請求成功率下降,這個並不是單例項可以解決的,因為由於作為主儲存,持久化是必須的。

所以我們期待一個多主多從的Redis系統,這樣無論作為主存還是作為快取,壓力和穩定性都會提升,儘管如此,筆者還是建議:

  1. Redis儘量不要做主儲存!
  2. Redis儘量不要做主儲存!
  3. Redis儘量不要做主儲存!

如果你一意孤行,那麼要麼坑了自己,要麼坑了別人。

  • 叢集與分片

要支援叢集首先要克服的就是分片問題,也就是一致性雜湊問題,常見的方案有三種:

  1. 客戶端分片:這種情況主要是類似於雜湊取模的做法,當客戶端對服務端的數量完全掌握和控制時,可以簡單使用。
  2. 中間層分片:這種情況是在客戶端和伺服器端之間增加中間層,充當管理者和排程者,客戶端的請求打向中間層,由中間層實現請求的轉發和回收,當然中間層最重要的作用是對多臺伺服器的動態管理。
  3. 服務端分片:不使用中間層實現去中心化的管理模式,客戶端直接向伺服器中任意結點請求,如果被請求的Node沒有所需資料,則像客戶端回覆MOVED,並告訴客戶端所需資料的儲存位置,這個過程實際上是客戶端和服務端共同配合,進行請求重定向來完成的。
  • 中間層分片的叢集版Redis

前面提到了變為N主N從可以有效提高處理能力和穩定性,但是這樣就面臨一致性雜湊的問題,也就是動態擴縮容時的資料問題。

在Redis官方釋出叢集版本之前,業內有一些方案迫不及待要用起自研版本的Redis叢集,其中包括國內豌豆莢的Codis、國外Twiter的twemproxy。

核心思想都是在多個Redis伺服器和客戶端Client中間增加分片層,由分片層來完成資料的一致性雜湊和分片問題,每一家的做法有一定的區別,但是要解決的核心問題都是多臺Redis場景下的擴縮容、故障轉移、資料完整性、資料一致性、請求處理延時等問題。

業內Codis配合LVS等多種做法實現Redis叢集的方案有很多都應用到生成環境中,表現都還不錯,主要是官方叢集版本在Redis3.0才出現,對其穩定性如何,很多公司都不願做小白鼠,不過事實上經過迭代目前已經到了Redis5.x版本,官方叢集版本還是很不錯的,至少筆者這麼認為。

  • 服務端分片的官方叢集版本

官方版本區別於上面的Codis和Twemproxy,實現了伺服器層的Sharding分片技術,換句話說官方沒有中間層,而是多個服務結點本身實現了分片,當然也可以認為實現sharding的這部分功能被融合到了Redis服務本身中,並沒有單獨的Sharding模組。

之前的文章也提到了官方叢集引入slot的概念進行資料分片,之後將資料slot分配到多個Master結點,Master結點再配置N個從結點,從而組成了多例項sharding版本的官方叢集架構。

Redis Cluster 是一個可以在多個 Redis 節點之間進行資料共享的分散式叢集,在服務端,通過節點之間的特殊協議進行通訊,這個特殊協議就充當了中間層的管理部分的通訊協議,這個協議稱作Gossip流言協議。

分散式系統一致性協議的目的就是為了解決叢集中多結點狀態通知的問題,是管理叢集的基礎。

如圖展示了基於Gossip協議的官方叢集架構圖:

注:圖片來自網路

2.Redis Cluster的基本執行原理

  • 結點狀態資訊結構

Cluster中的每個節點都維護一份在自己看來當前整個叢集的狀態,主要包括:

  1. 當前叢集狀態
  2. 叢集中各節點所負責的slots資訊,及其migrate狀態
  3. 叢集中各節點的master-slave狀態
  4. 叢集中各節點的存活狀態及不可達投票

也就是說上面的資訊,就是叢集中Node相互八卦傳播流言蜚語的內容主題,而且比較全面,既有自己的更有別人的,這麼一來大家都相互傳,最終資訊就全面而且準確了,區別於拜占庭帝國問題,資訊的可信度很高。

基於Gossip協議當叢集狀態變化時,如新節點加入、slot遷移、節點宕機、slave提升為新Master,我們希望這些變化儘快的被發現,傳播到整個叢集的所有節點並達成一致。節點之間相互的心跳(PING,PONG,MEET)及其攜帶的資料是叢集狀態傳播最主要的途徑。

  • Gossip協議的概念
gossip 協議(gossip protocol)又稱 epidemic 協議(epidemic protocol),是基於流行病傳播方式的節點或者程序之間資訊交換的協議。
在分散式系統中被廣泛使用,比如我們可以使用 gossip 協議來確保網路中所有節點的資料一樣。
gossip protocol 最初是由施樂公司帕洛阿爾託研究中心(Palo Alto Research Center)的研究員艾倫·德默斯(Alan Demers)於1987年創造的。https://www.iteblog.com/archives/2505.html

Gossip協議已經是P2P網路中比較成熟的協議了。Gossip協議的最大的好處是,即使叢集節點的數量增加,每個節點的負載也不會增加很多,幾乎是恆定的。這就允許Consul管理的叢集規模能橫向擴充套件到數千個節點。

Gossip演算法又被稱為反熵(Anti-Entropy),熵是物理學上的一個概念,代表雜亂無章,而反熵就是在雜亂無章中尋求一致,這充分說明了Gossip的特點:在一個有界網路中,每個節點都隨機地與其他節點通訊,經過一番雜亂無章的通訊,最終所有節點的狀態都會達成一致。每個節點可能知道所有其他節點,也可能僅知道幾個鄰居節點,只要這些節可以通過網路連通,最終他們的狀態都是一致的,當然這也是疫情傳播的特點。https://www.backendcloud.cn/2017/11/12/raft-gossip/

上面的描述都比較學術,其實Gossip協議對於我們吃瓜群眾來說一點也不陌生,Gossip協議也成為流言協議,說白了就是八卦協議,這種傳播規模和傳播速度都是非常快的,你可以體會一下。所以計算機中的很多演算法都是源自生活,而又高於生活的。

  • Gossip協議的使用

Redis 叢集是去中心化的,彼此之間狀態同步靠 gossip 協議通訊,叢集的訊息有以下幾種型別:

  1. Meet 通過「cluster meet ip port」命令,已有叢集的節點會向新的節點發送邀請,加入現有叢集。
  2. Ping 節點每秒會向叢集中其他節點發送 ping 訊息,訊息中帶有自己已知的兩個節點的地址、槽、狀態資訊、最後一次通訊時間等。
  3. Pong 節點收到 ping 訊息後會回覆 pong 訊息,訊息中同樣帶有自己已知的兩個節點資訊。
  4. Fail 節點 ping 不通某節點後,會向叢集所有節點廣播該節點掛掉的訊息。其他節點收到訊息後標記已下線。

由於去中心化和通訊機制,Redis Cluster 選擇了最終一致性和基本可用。

例如當加入新節點時(meet),只有邀請節點和被邀請節點知道這件事,其餘節點要等待 ping 訊息一層一層擴散。除了 Fail 是立即全網通知的,其他諸如新節點、節點重上線、從節點選舉成為主節點、槽變化等,都需要等待被通知到,也就是Gossip協議是最終一致性的協議。

由於 gossip 協議對伺服器時間的要求較高,否則時間戳不準確會影響節點判斷訊息的有效性。另外節點數量增多後的網路開銷也會對伺服器產生壓力,同時結點數太多,意味著達到最終一致性的時間也相對變長,因此官方推薦最大節點數為1000左右。如圖展示了新加入結點伺服器時的通訊互動圖:

注:圖片來自網路

總起來說Redis官方叢集是一個去中心化的類P2P網路,P2P早些年非常流行,像電驢、BT什麼的都是P2P網路。在Redis叢集中Gossip協議充當了去中心化的通訊協議的角色,依據制定的通訊規則來實現整個叢集的無中心管理節點的自治行為。

  • 基於Gossip協議的故障檢測

叢集中的每個節點都會定期地向叢集中的其他節點發送PING訊息,以此交換各個節點狀態資訊,檢測各個節點狀態:線上狀態、疑似下線狀態PFAIL、已下線狀態FAIL。

自己儲存資訊:當主節點A通過訊息得知主節點B認為主節點D進入了疑似下線(PFAIL)狀態時,主節點A會在自己的clusterState.nodes字典中找到主節點D所對應的clusterNode結構,並將主節點B的下線報告新增到clusterNode結構的fail_reports連結串列中,並後續關於結點D疑似下線的狀態通過Gossip協議通知其他節點。

一起裁定:如果叢集裡面,半數以上的主節點都將主節點D報告為疑似下線,那麼主節點D將被標記為已下線(FAIL)狀態,將主節點D標記為已下線的節點會向叢集廣播主節點D的FAIL訊息,所有收到FAIL訊息的節點都會立即更新nodes裡面主節點D狀態標記為已下線。

最終裁定:將 node 標記為 FAIL 需要滿足以下兩個條件:

  1. 有半數以上的主節點將 node 標記為 PFAIL 狀態。
  2. 當前節點也將 node 標記為 PFAIL 狀態。

也就是說當前節點發現其他結點疑似掛掉了,那麼就寫在自己的小本本上,等著通知給其他好基友,讓他們自己也看看,最後又一半以上的好基友都認為那個節點掛了,並且那個節點自己也認為自己掛了,那麼就是真的掛了,過程還是比較嚴謹的。

3.參考資料

    1. https://cloud.tencent.com/developer/article/1398245
    2. https://www.zhihu.com/question/21419897
    3. https://www.backendcloud.cn/2017/11/12/raft-gossip/
    4. https://www.cnblogs.com/zhoujinyi/p/11637483.html
    5. https://catkang.github.io/2016/05/08/redis-cluster-source.html

&n