淺談集羣版Redis和Gossip協議

今天來看一下Redis-Cluster和其中的重要概念Gossip協議。

1.Redis Cluster的基本概念

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

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

單實例一主兩從+讀寫分離結構:
淺談集羣版Redis和Gossip協議

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

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

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

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

Redis儘量不要做主存儲!
Redis儘量不要做主存儲!
Redis儘量不要做主存儲!
如果你一意孤行,那麼要麼坑了自己,要麼坑了別人。

集羣與分片
要支持集羣首先要克服的就是分片問題,也就是一致性哈希問題,常見的方案有三種:

客戶端分片:這種情況主要是類似於哈希取模的做法,當客戶端對服務端的數量完全掌握和控制時,可以簡單使用。
中間層分片:這種情況是在客戶端和服務器端之間增加中間層,充當管理者和調度者,客戶端的請求打向中間層,由中間層實現請求的轉發和回收,當然中間層最重要的作用是對多臺服務器的動態管理。
服務端分片:不使用中間層實現去中心化的管理模式,客戶端直接向服務器中任意結點請求,如果被請求的Node沒有所需數據,則像客戶端回覆MOVED,並告訴客戶端所需數據的存儲位置,這個過程實際上是客戶端和服務端共同配合,進行請求重定向來完成的。
中間層分片的集羣版Redis
前面提到了變爲N主N從可以有效提高處理能力和穩定性,但是這樣就面臨一致性哈希的問題,也就是動態擴縮容時的數據問題。

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

核心思想都是在多個Redis服務器和客戶端Client中間增加分片層,由分片層來完成數據的一致性哈希和分片問題,每一家的做法有一定的區別,但是要解決的核心問題都是多臺Redis場景下的擴縮容、故障轉移、數據完整性、數據一致性、請求處理延時等問題。
淺談集羣版Redis和Gossip協議

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

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

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

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

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

如圖展示了基於Gossip協議的官方集羣架構圖:
淺談集羣版Redis和Gossip協議

2.Redis Cluster的基本運行原理

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

當前集羣狀態
集羣中各節點所負責的slots信息,及其migrate狀態
集羣中各節點的master-slave狀態
集羣中各節點的存活狀態及不可達投票
也就是說上面的信息,就是集羣中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年創造的。

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

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

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

Gossip協議的使用
Redis 集羣是去中心化的,彼此之間狀態同步靠 gossip 協議通信,集羣的消息有以下幾種類型:

Meet 通過「cluster meet ip port」命令,已有集羣的節點會向新的節點發送邀請,加入現有集羣。
Ping 節點每秒會向集羣中其他節點發送 ping 消息,消息中帶有自己已知的兩個節點的地址、槽、狀態信息、最後一次通信時間等。
Pong 節點收到 ping 消息後會回覆 pong 消息,消息中同樣帶有自己已知的兩個節點信息。
Fail 節點 ping 不通某節點後,會向集羣所有節點廣播該節點掛掉的消息。其他節點收到消息後標記已下線。
由於去中心化和通.信機制,Redis Cluster 選擇了最終一致性和基本可用。

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

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

總起來說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 需要滿足以下兩個條件:

有半數以上的主節點將 node 標記爲 PFAIL 狀態。
當前節點也將 node 標記爲 PFAIL 狀態。
也就是說當前節點發現其他結點疑似掛掉了,那麼就寫在自己的小本本上,等着通知給其他好友,讓他們自己也看看,最後又一半以上的好友都認爲那個節點掛了,並且那個節點自己也認爲自己掛了,那麼就是真的掛了,過程還是比較嚴謹的。
淺談集羣版Redis和Gossip協議

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章