理想化的 Redis 叢集

類別: IT

一個豁達的關鍵是正確樂觀的面對失敗的系統。不需要過多的擔心,需要一種去說那又怎樣的能力。因此架構的設計是如此的重要。許多優秀的系統沒有進一步成長的能力,我們應該做的是去使用其他的系統去共同分擔工作。

Redis是其中一個吸引我的系統,一個永續性的,鍵值對儲存記憶體操作高效能的平臺。他是一個優秀的鍵值對資料庫。我已經在使用了。即使AWS最近宣佈開始支援ElasticCache的下級快取。但是一個無主的redis叢集仍然起著重要的作用。我們需要多系統去完成工作。同時,我們能夠集合多種元件在一個容錯和無主的叢集裡共同工作麼?在這片文章中我將介紹夢幻般的redis。

一致雜湊

構建一個儲存資料叢集的關鍵是有一個有效的資料儲存和複製機制。我希望通過一個行之有效的方法來說明建造一個資料叢集,在這個過程中你可以隨意新增或移除一個Redis節點,同時保證你的資料仍然存在,而不會消失。這個方法稱為一致雜湊。

由於它不是一個很明顯的概念,我將用一點時間來解釋一下。為了理解一致雜湊,你可以想像有一個函式f(x),對於給定的x總是返回一個1到60(為什麼是60?你會知道的,但現在請等等)之間的結果,同樣對於一個唯一的x,f(x)總是返回相同的結果。這些1到60的值按順時針排成一個環。

image


現在叢集中的每個節點都需要一個唯一的名字,是吧?所以如果你將這個名字傳遞給f('<redis_node_name>'),它將返回一個1到60之間的數字(包括1和60),這個數字就是節點在環上的位置。當然它只是節點的邏輯(記錄的)位置。這樣,你獲得一個節點,將它傳給雜湊函式,獲得結果並將它放到環上。是不是很簡單?這樣每個節點都在環上有了它自己的位置。假設這裡有5個Redis節點,名字分別為'a','b','c','d','e'。每個節點都傳給雜湊函式f(x)並且放到了環上。在這裡f('a') = 21, f('b') = 49, f('c') = 11, f('d') = 40, f('e') = 57。一定記得這裡位置是邏輯位置。

image

那麼,我們為什麼要將節點放在一個環上呢?將節點放到環上的目的是確定擁有哪些雜湊空間。圖中的節點'd'擁有的雜湊空間就是f('a')到f('d')(其值為40)之間的部分,包括f('d'),即(21, 40]。也就是說節點'd'將擁有鍵x,如果f(x)的屬於區間(21, 40]。比如鍵‘apple’,其值f('apple') = 35,那麼鍵'apple'將被存在'd'節點。類似的,每個儲存在叢集上的鍵都會通過雜湊函式,在環上按順時針方向被恰當地存到最近的節點。

image

雖然一致雜湊講完了,但應知道,在多數情況下,這種型別的系統是伴隨著高可用性而構建。為了滿足資料的高可用性,需要根據一些因子進行復制, 這些因子稱為複製因子。假設我們叢集的複製因子為2,那麼屬於'd'節點的資料將會被複制到按順時針方向與之相隔最近的'b'和'e'節點上。這就保證瞭如果從'd'節點獲取資料失敗,這些資料能夠從'b'或'e'節點獲取。

image

不僅僅是鍵使用一致雜湊來儲存,也很容易覆蓋失敗了的節點,並且複製因子依然完好有效。比如'd'節點失敗了,'b'節點將獲取'd'節點雜湊空間的所有權,同時'd'節點的雜湊空間能夠很容易地複製到'c'節點。

image

壞事和好事

壞事就是目前這些討論過的所有概念,複製(冗餘),失效處理以及叢集規模等,在Redis之外是不可行的。一致雜湊僅僅描述了節點在雜湊環上的對映以及那些雜湊資料的所有權,儘管這樣,它仍然是構建一個彈性可擴充套件系統的極好的開端。

好事就是,也有一些分立的其他工具在Redis叢集上實現一致雜湊,它們能提醒節點失效和新節點的加入。雖然這個功能不是一個工具的一部分,我們將看到如何用多個系統來使一個理想化的Redis叢集運轉起來。

Twemproxy aka Nutcracker

Twemproxy是一個開源工具,它是一個基於memcached和Redis協議的快速、輕量的代理。其本質就是,如果你有一些Redis伺服器在執行,同時希望用這些伺服器構建叢集,你只需要將Twemproxy部署在這些伺服器前端,並且讓所有Redis流量都通過它。

Twemproxy除了能夠代理Redis流量外,在它儲存資料在Redis伺服器時還能進行一致雜湊。這就保證了資料被分佈在基於一致雜湊的多個不同Redis節點上。

image

但是Twemproxy並沒有為Redis叢集建立高可用性支援。最簡單的辦法是為叢集中的每個節點都建立一個從(冗餘)伺服器,當主伺服器失效時將從(冗餘)伺服器提升為主伺服器。為Redis配置一個從伺服器是非常簡單的。

image

這種模型的缺點是非常明顯的,它需要為Redis叢集中的每個節點同時執行兩個伺服器。但是節點失效也是非常明顯,並且更加危險,所以我們怎麼知道這些問題並解決。

Gossip on Serf

Gossip是一個標準的機制,通過這個機制叢集上的節點可以很清楚的瞭解成員的最新情況。這樣子叢集中的每個節點就很清楚叢集中節點的變化,如節點的新增和節目的刪除。

Serf通過實現Gossip機制提供這樣的幫助。Serf是一個基於代理的機制,這個機制實現了Gossip的協議達到節點成員資訊交換的目的。Serf是不斷執行,除此之外,它還可以生成自定義的事件

現在拿我們的節點叢集為例,如果每個節點上也有一個serf代理正在執行,那麼節點與節點之間可以進行細節交換.因此,群集中的每個節點都能清楚知道其他節點的存在,也能清楚知道他們的狀態。

image

這還並不夠,為了高可靠性我們還需要讓twemproxy知道何時一個節點已經失效,這樣的話它就可以據此修改它的配置。像前面提到的Serf,就可以做到這一點,它是基於一些gossip觸發的事件,使用自定義動作實現的。因此只要叢集中的一個Redis節點因為一些原因宕掉了,另一個節點就可以傳送有成員意外掉線的訊息給任何給定的端點,這個端點在我們的案例中也就是twemproxy伺服器。

image

這還不是全部

現在我們有了Redis叢集,基於一致性雜湊環,相應的是用twemproxy儲存資料(一致性雜湊),還有Serf,它用gossip協議來檢測Redis叢集成員失效,並且向twemproxy傳送失效訊息;但是我們還沒有建立起理想化的Redis叢集。

訊息偵聽器

雖然Serf可以給任何端點傳送成員離線或者成員上線訊息。然而twemproxy卻沒有偵聽此類事件的機制。因此我們需要自定義一個偵聽器,就像Redis-Twenproxy代理,它需要做以下這些事情。

  • 偵聽Serf訊息
  • 更新nutcraker.yml 以反映新的拓撲
  • 重啟twemproxy

這個訊息偵聽器可以是一個小型的http伺服器;它在收到一批POST資料的時候,為twemproxy做以上列表中的動作。需要記住的是,這種訊息應該是一個原子操作;因為當一個節點失效(或者意外離線)的時候,所有能發訊息的活動節點都將傳送這個失效事件訊息給偵聽器;但是偵聽器應該只響應一次。

資料複製

在上面的“一致雜湊”中,我提到了Redis叢集中的複製因素。同樣它也不是Twemproxy的固有特性;Twemproxy只關心使用一致雜湊儲存一個拷貝。所以在我們追求理想化Redis叢集的過程中,我們還需要給twemproxy或者redis自己建立這種複製的能力。

為了給Twenproxy建立複製能力,需要將複製因子作為一個配置專案,並且將資料儲存在叢集中相鄰的redis節點(根據複製因子)。由於twemproxy知道節點的位置,所以這將給twemproxy增加一個很棒的功能。

image

由於twemproxy只不過是代理伺服器,它的簡單功能就是它強大的地方,為它建立複製管理功能將使它臃腫膨脹。

Redis 主從環

在思考這其中的工作機制的時候,我忽然想到,為什麼不將每個節點設定成另一個節點的副本,或者說從節點,並由此而形成一個主從環呢?

image

這樣的話如果一個節點失效了,失效節點的資料在這個環上相鄰的節點上仍然可以獲得。而那個具有該資料副本的節點,將作為該節點的從節點,並提供儲存資料副本的服務。這是一個既是主節點也是從節點的環。Serf仍像通常一樣作為散佈節點失效訊息的代理。但是這一回,我們twenproxy上的的客戶端,即偵聽器,將不僅僅只更新twenproxy上的失效資訊,還要調整redis伺服器群集來適應這個變化。

image

在這個環中有一個明顯的,同樣也是技術方面的缺陷。這個明顯的缺陷是,這個環會壞掉,因為從節點的從節點無法判別哪一個是它的主節點的資料,哪一個是它的主節點的主節點的資料。這樣的話就會迴圈的傳送所有的資料。

image

同樣技術性的問題是,一旦redis將主節點的資料同步給從節點,它就會將從節點的資料擦除乾淨;這樣就將曾經寫到從節點的所有資料刪除了。這種主從環顯然不能實際應用,除非修改主從環的複製機制以適應我們的需求。這樣的話每個從節點就不會將它的主節點的資料同步給它的從節點。要想實現這一點,必須的條件是每個節點都可以區分出自己的金鑰空間,以及它的主節點的金鑰空間;這樣的話它就不會將主節點的資料傳送給它的從節點。

image

那麼這樣一來,當一個節點失效時,就需要執行四個動作。一,將失效節點的從節點作為它的金鑰空間的所有者。二,將這些金鑰散佈給失效節點從節點的從節點,以便進行復制。三,將失效節點的從節點作為失效節點的主節點的從節點。最後,在新的拓撲上覆位twemproxy。

image

如何理想化?

事實上並沒有這樣的Redis叢集,它可以具有一致性的雜湊,高可靠性以及分割槽容錯性。因此最後一幅圖片描繪了一種理想化的Redis叢集;但這並不是不可能的。接下來將羅列一下需要哪些條件才能使之成為一個實實在在的產品。

透明的Twenproxy

有必要部署一個Twenproxy,這會使得Hash雜湊中Redis各節點的位置都是透明的。每個Redis節點都可以知道自己以及其相鄰節點的位置,這些資訊對於節點的主從複製以及失敗節點的修復是有必要的。自從Twenproxy開源之後,節點的位置資訊可以被修改、以及擴充套件。

Redis的資料擁有權

這是比較困難的部分的,每個Redis節點都應該記錄自身擁有的資料,以及哪些是主節點的資料。當前這樣的隔離是不可能的。這也需要修改Redis的基礎程式碼,這樣節點才知道何時與從節點同步,什麼時候不需要。

綜上所述,我們的理想化的Redis叢集變成現實需要修改這樣的兩個元件。一直以來,它們都是非常大的工業級別,使用在生產環境中。這已經值得任何人去實現(這個叢集了)。

理想化的 Redis 叢集原文請看這裡