1. 程式人生 > >redis叢集方案-一致性hash演算法

redis叢集方案-一致性hash演算法

前奏

叢集的概念早在 Redis 3.0 之前討論了,3.0 才在原始碼中出現。Redis 叢集要考慮的問題:

  1. 節點之間怎麼據的同步,如何做到資料一致性。一主一備的模式,可以用 Redis 內部實現的主從備份實現資料同步。但節點不斷增多,存在多個 master 的時候,同步的難度會越大。
  2. 如何做到負載均衡?請求量大的時候,如何將請求儘量均分到各個伺服器節點,負載均衡演算法做的不好會導致雪崩。
  3. 如何做到平滑拓展?當業務量增加的時候,能否通過簡單的配置即讓新的 Redis 節點變為可用。
  4. 可用性如何?當某些節點鼓掌,能否快速恢復伺服器叢集的工作能力。
  5. ……

一個穩健的後臺系統需要太多的考慮。

也談一致性雜湊演算法(consistent hashing)

背景

通常,業務量較大的時候,考慮到效能的問題(索引速度慢和訪問量過大),不會把所有的資料存放在一個 Redis 伺服器上。這裡需要將一堆的鍵值均分儲存到多個 Redis 伺服器,可以通過:

target = hash(key)\%N,其中 target 為目標節點,key 為鍵,N 為 Redis 節點的個數雜湊取餘的方式會將不同的 key 分發到不同的伺服器上。

但考慮如下場景:

  1. 業務量突然增加,現有伺服器不夠用。增加伺服器節點後,依然通過上面的計算方式:hash(key)%(N+1) 做資料分片和分發,但之前的 key 會被分發到與之前不同的伺服器上,導致大量的資料失效,需要重新寫入(set)Redis 伺服器。
  2. 其中的一個伺服器掛了。如果不做及時的修復,大量被分發到此伺服器請求都會失效。

這也是兩個問題。

一致性雜湊演算法


設定一個圓環上 0-2^3̂2-1 的點,每個點對應一個快取區,每個鍵值對儲存的位置也經雜湊計算後對應到環上節點。但現實中不可能有如此多的節點,所以倘若鍵值對經雜湊計算後對應的位置沒有節點,那麼順時針找一個節點儲存它。

考慮增加伺服器節點的情況,該節點順時針方向的資料仍然被儲存到順時針方向的節點上,但它逆時針方向的資料被儲存到它自己。這時候只有部分資料會失效,被對映到新的快取區。

考慮節點減少的情況。該缺失節點順時針方向上的資料仍然被儲存到其順時針方向上的節點,設為 beta,其逆時針方向上的資料會被儲存到 beta 上。同樣,只有有部分資料失效,被重新對映到新的伺服器節點。


這種情況比較麻煩,上面圖中 gamma 節點失效後,會有大量資料對映到 alpha 節點,最怕 alpha 扛不住,接下去 beta 也扛不住,這就是多米諾骨牌效應;)。這裡涉及到資料平衡性和負載均衡的話題。資料平衡性是說,資料儘可能均分到每個節點上去,儲存達到均衡。
虛擬節點簡介

將多個虛擬節點對應到一個真實的節點,儲存可以達到更均衡的效果。之前的對映方案為:

key -> node

中間多了一個層虛擬節點後,多了一層對映關係:

key -> <virtual node> -> node

為什麼需要虛擬節點

虛擬節點的設計有什麼好處?假設有四個節點如下:


節點 3 突然宕機,這時候原本在節點 3 的資料,會被定向到節點 4。在三個節點中節點 4 的請求量是最大的。這就導致節點與節點之間請求量是不均衡的。

為了達到節點與節點之間請求訪問的均衡,嘗試將原有節點 3 的資料平均定向到到節點 1,2,4. 如此達到負載均衡的效果,如下:

總之,一致性雜湊演算法是希望在增刪節點的時候,讓儘可能多的快取資料不失效。

怎麼實現?

一致性雜湊演算法,既可以在客戶端實現,也可以在中介軟體上實現(如 proxy)。在客戶端實現中,當客戶端初始化的時候,需要初始化一張預備的 Redis 節點的對映表:hash(key)=> . 這有一個缺點,假設有多個客戶端,當對映表發生變化的時候,多個客戶端需要同時拉取新的對映表。

另一個種是中介軟體(proxy)的實現方法,即在客戶端和 Redis 節點之間加多一個代理,代理經過雜湊計算後將對應某個 key 的請求分發到對應的節點,一致性雜湊演算法就在中介軟體裡面實現。可以發現,twemproxy 就是這麼做的。

twemproxy - Redis 叢集管理方案

twemproxy 是 twitter 開源的一個輕量級的後端代理,相容 redis/memcache 協議,可用以管理 redis/memcache 叢集。


twemproxy 內部有實現一致性雜湊演算法,對於客戶端而言,twemproxy 相當於是快取資料庫的入口,它無需知道後端的部署是怎樣的。twemproxy 會檢測與每個節點的連線是否健康,出現異常的節點會被剔除;待一段時間後,twemproxy 會再次嘗試連線被剔除的節點。

通常,一個 Redis 節點池可以分由多個 twemproxy 管理,少數 twemproxy 負責寫,多數負責讀。twemproxy 可以實時獲取節點池內的所有 Redis 節點的狀態,但其對故障修復的支援還有待提高。解決的方法是可以藉助 redis sentinel 來實現自動的主從切換,當主機 down 掉後,sentinel 會自動將從機配置為主機。而 twemproxy 可以定時向 redis sentinel 拉取資訊,從而替換出現異常的節點。


twemproxy 的更多細節,這裡不再做深入的討論。

Redis 官方版本支援的叢集

最新版本的 Redis 也開始支援叢集特性了,再也不用靠著外援過日子了。基本的思想是,叢集裡的每個 Redis 都只儲存一定的鍵值對,這個“一定”可以通過預設或自定義的雜湊函式來決定,當一個 Redis 收到請求後,會首先檢視此鍵值對是否該由自己來處理,是則繼續往下執行;否則會產生一個類似於 http 3XX 的重定向,要求客戶端去請求叢集中的另一個 Redis。

Redis 每一個例項都會通過遵守一定的協議來維護這個叢集的可用性,穩定性。有興趣可前往官網瞭解 Redis 叢集的實現細則。