淺析分佈式緩存彈性擴容下的一致性哈希算法

序曲:

本期不講小程序,講分佈式哈!!!工作久了,容易在自己狹小的領域裏停滯不前。爲了跳出舒適圈,我時常觀看一些互聯網上的直播課程,以便持續更新自己的技術。當然了,這些課程都是採取了免費+付費的策略。初始都是免費給你看一個直播系列課程,如果你稍稍變得對講師畫的藍圖感興趣,就要花費8000+以上學費以求短時間內練就神功。我發現這些講師有一個共性,就是都喜歡用大保健來做比喻,以至於技師這個詞出現的頻次遠高於技術,大概因爲觀衆中女程序員比較少,講師也無所顧忌。

正文:

我最近看的一期是利用一致性哈希算法來解決分佈式緩存擴容帶來的緩存雪崩的問題,我們來一起探討下。話不多說,先上2張圖鎮文。

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

假設我們有一個網站,併發訪問量是非常高的。直接讀寫數據庫的方式肯定不能及時處理用戶的大量請求,要知道3秒不返回,53%的用戶可能就關掉頁面離開了。爲了降低數據庫的訪問壓力,於是我們用nginx作負載均衡,引入Redis作爲緩存機制(略掉應用服務器部分)。現在我們一共有三臺機器可以作爲Redis服務器,如上圖所示。

既然有3臺機器,對於海量用戶的每次訪問,我們可以按照 h = Hash(key) % 3 算法簡易計算其哈希值,那麼如果我們將Redis Server分別編號爲0、1、2,就可以根據上式和key計算出服務器編號h,然後去訪問。大概數據的緩存就如下圖這個樣子。

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

老鐵想想看,這樣緩存數據可以想對均勻的分佈在Redis Server上,這樣真的就萬事大吉了麼? 對於熱點數據或者近期數據,緩存在大部分時候都可以工作得很好,然而當機器需要擴容或者機器出現宕機的情況下,事情就比較棘手了,它的容錯性和擴展性將會變得極差。

假如某天網站的訪問劇增,我們需要增加一臺機器來應對,假設機器由3臺變成4臺。因爲取模的變化(0,1,2變成0,1,2,3),會導致原來緩存的數據的機器,與重新計算的hash值不一致。如果你仔細計算一下的話,機器由3臺變成4臺,大約有75%(3/4)的可能性出現緩存訪問不命中的現象(例如數據緩存在Node3 ,按照新的hash值,你去訪問Node4,顯然是拿不到數據)。

隨着機器集羣規模的擴大,這個比例線性上升。當99臺機器再加入1臺機器時,不命中的概率是99%(99/100=====n=/n+m)。這樣的結果顯然是不能接受的,因爲這會導致數據庫訪問的壓力陡增,嚴重情況還可能導致數據庫宕機(即發生緩存雪崩:緩存不可用或者大量緩存由於超時時間相同在同一時間段失效,大量請求直接訪問數據庫,數據庫壓力過大導致系統雪崩)。

----分割線------

普通Hash算法的劣勢,即當node數發生變化(增加、移除)後,數據項會被重新“打散”,導致大部分數據項不能落到原來的節點上,從而導致大量數據需要遷移。

那麼,一個亟待解決的問題就變成了:當node數發生變化時,如何保證儘量少引起遷移呢?即當增加或者刪除節點時,對於大多數item,保證原來分配到的某個node,現在仍然應該分配到那個node,將數據遷移量的降到最低

我們利用一致性hash算法來解決之前的緩存雪崩的問題,一致性哈希算法(Consistent Hashing)最早在論文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。簡單來說,一致性哈希將整個哈希值空間組織成一個虛擬的圓環,如假設某哈希函數H的值空間爲0-2^32-1(即哈希值是一個32位無符號整形),這個環的起點是0,終點是2^32 - 1,並且起點與終點連接,環的中間的整數按逆時針分佈,故這個環的整數分佈範圍是[0, 2^32-1]。

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

那麼老鐵會說了,圓你也畫好了,還帶倆“天線”呢,但是我就想知道數據怎麼緩存?我們先把三臺Redis機器給它掛到環上去。

(這裏我們可以選擇服務器的ip或主機名作爲關鍵字進行哈希,這樣每臺機器就能確定其在哈希環上的位置,這裏假設將上文中三臺服務器使用ip地址哈希後在環空間的位置如上圖,當然這比較理想化,事實上肯定不是均勻分佈在環上)

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

好了,機器掛上去了,現在我們放置待緩存的數據。

數據緩存的方式:將數據key使用相同的函數H計算出哈希值h,通根據h確定此數據在環上的位置,從此位置沿環順時針“行走”,第一臺遇到的服務器就是其應該定位到的服務器。

例如我們緩存服務器中有A、B、C、D四個key對應的數據對象,經過哈希計算後,在環空間上的位置如下圖,

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

如果我們將緩存節點和待緩存的數據關係進一步抽象,將會是類似下面這張圖:

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

那麼顯然的,A、B、C、D四個野孩子按照順時針找到各自的歸宿,分別是A=>Redis-1 ,B=>Redis-2 ,C、D =>Redis-0 。

好了,一切工作的是那麼完美,我們現在考慮另外一種情況,如果我們在系統中增加一臺服務器Redis-3 Server來擴容:

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

可以發現對於C這個key,重新定位至Redis-3 服務器,其他非C的key均不受影響。如果一臺機器宕機了,情況也是類似的,我就不上圖了。

如上文前面所述,使用簡單的求模方法,當新添加機器後會導致大部分緩存失效的情況,使用一致性hash算法後這種情況則會得到大大的改善。前面提到3臺機器變成4臺機器後,緩存命中率只有25%(不命中率75%)。而使用一致性hash算法,理想情況下緩存命中率則有75%,而且,隨着機器規模的增加,命中率會進一步提高,99臺機器增加一臺後,命中率達到99%,這大大減輕了增加緩存機器帶來的數據庫訪問的壓力。

我們的一致性哈希算法的大招如同降龍十八掌,我們剛纔纔打到第十七式,最後一招是虛擬節點。

在服務節點太少時,容易因爲節點分部不均勻而造成數據傾斜問題。例如我們的系統中有兩臺服務器,其環分佈如下:

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

此時必然造成大量數據集中到Redis-1上,而只有極少量會定位到Redis-0上。爲了解決這種數據傾斜問題,一致性哈希算法引入了虛擬節點機制,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱爲虛擬節點(虛擬節點技術實則是做了兩次matching)。具體做法可以在服務器ip或主機名的後面增加編號來實現。例如上面的情況,我們決定爲每臺服務器計算三個虛擬節點,於是可以分別計算“Redis-1 #1”、“Redis-1 #2”、“Redis-1 #3”、“Redis-0 #1”、“Redis-0 #2”、“Redis-0 #3”的哈希值,於是形成六個虛擬節點:

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

但是6個虛擬節點顯然是不能滿足海量數據的緩存需要的,在實際應用中,通常將虛擬節點數設置爲32甚至更大,因此即使很少的服務節點也能做到相對均勻的數據分佈。

好了,打完收工。雖然是個知識搬運工,但爲了加深自己的印象,我也加入了很多個人理解,希望各位看官手下留情,關注技術本身,希望對你有所裨益。能讀到這的,算是真老鐵了,我送一張 利用Caffeine做一級緩存,Redis作爲二級緩存的美麗的圖,給你加餐。

淺析分佈式緩存彈性擴容下的一致性哈希算法

 

老鐵別急着走,關注我微信公衆號和頭條號——“前端琅琊閣”,回覆“hash”,可以獲取延伸閱讀 

     

或者向我提問:

向我提問

歌德說:“向着某一天終於要達到的那個終極目標邁步還不夠,還要把每一步驟看成目標,使它作爲步驟而起作用。” 每天完成一小步,我們一起進步。直到有一天,會當凌絕頂,一覽衆山小。

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