在瞭解一致性哈希算法以前,最好先了解一下緩存中的一個應用場景,瞭解了這個應用場景以後,再來理解一致性哈希算法,就容易多了,也更能體現出一致性哈希算法的優勢,那麼,咱們先來描述一下這個經典的分佈式緩存的應用場景。
場景描述
假設,咱們有三臺緩存服務器,用於緩存圖片,咱們爲這三臺緩存服務器編號爲0號、1號、2號,如今,有3萬張圖片須要緩存,咱們但願這些圖片被均勻的緩存到這3臺服務器上,以便它們可以分攤緩存的壓力。也就是說,咱們但願每臺服務器可以緩存1萬張左右的圖片,那麼,咱們應該怎樣作呢?若是咱們沒有任何規律的將3萬張圖片平均的緩存在3臺服務器上,能夠知足咱們的要求嗎?能夠!可是若是這樣作,當咱們須要訪問某個緩存項時,則須要遍歷3臺緩存服務器,從3萬個緩存項中找到咱們須要訪問的緩存,遍歷的過程效率過低,時間太長,當咱們找到須要訪問的緩存項時,時長多是不能被接受的,也就失去了緩存的意義,緩存的目的就是提升速度,改善用戶體驗,減輕後端服務器壓力,若是每次訪問一個緩存項都須要遍歷全部緩存服務器的全部緩存項,想一想就以爲很累,那麼,咱們該怎麼辦呢?原始的作法是對緩存項的鍵進行哈希,將hash後的結果對緩存服務器的數量進行取模操做,經過取模後的結果,決定緩存項將會緩存在哪一臺服務器上,這樣說可能不太容易理解,咱們舉例說明,仍然以剛纔描述的場景爲例,假設咱們使用圖片名稱做爲訪問圖片的key,假設圖片名稱是不重複的,那麼,咱們可使用以下公式,計算出圖片應該存放在哪臺服務器上。
hash(圖片名稱)% N
由於圖片的名稱是不重複的,因此,當咱們對同一個圖片名稱作相同的哈希計算時,得出的結果應該是不變的,若是咱們有3臺服務器,使用哈希後的結果對3求餘,那麼餘數必定是0、1或者2,沒錯,正好與咱們以前的服務器編號相同,若是求餘的結果爲0, 咱們就把當前圖片名稱對應的圖片緩存在0號服務器上,若是餘數爲1,就把當前圖片名對應的圖片緩存在1號服務器上,若是餘數爲2,同理,那麼,當咱們訪問任意一個圖片的時候,只要再次對圖片名稱進行上述運算,便可得出對應的圖片應該存放在哪一臺緩存服務器上,咱們只要在這一臺服務器上查找圖片便可,若是圖片在對應的服務器上不存在,則證實對應的圖片沒有被緩存,也不用再去遍歷其餘緩存服務器了,經過這樣的方法,便可將3萬張圖片隨機的分佈到3臺緩存服務器上了,並且下次訪問某張圖片時,直接可以判斷出該圖片應該存在於哪臺緩存服務器上,這樣就能知足咱們的需求了,咱們暫時稱上述算法爲HASH算法或者取模算法,取模算法的過程能夠用下圖表示。
可是,使用上述HASH算法進行緩存時,會出現一些缺陷,試想一下,若是3臺緩存服務器已經不能知足咱們的緩存需求,那麼咱們應該怎麼作呢?沒錯,很簡單,多增長兩臺緩存服務器不就好了,假設,咱們增長了一臺緩存服務器,那麼緩存服務器的數量就由3臺變成了4臺,此時,若是仍然使用上述方法對同一張圖片進行緩存,那麼這張圖片所在的服務器編號一定與原來3臺服務器時所在的服務器編號不一樣,由於除數由3變爲了4,被除數不變的狀況下,餘數確定不一樣,這種狀況帶來的結果就是當服務器數量變更時,全部緩存的位置都要發生改變,換句話說,當服務器數量發生改變時,全部緩存在必定時間內是失效的,當應用沒法從緩存中獲取數據時,則會向後端服務器請求數據,同理,假設3臺緩存中忽然有一臺緩存服務器出現了故障,沒法進行緩存,那麼咱們則須要將故障機器移除,可是若是移除了一臺緩存服務器,那麼緩存服務器數量從3臺變爲2臺,若是想要訪問一張圖片,這張圖片的緩存位置一定會發生改變,之前緩存的圖片也會失去緩存的做用與意義,因爲大量緩存在同一時間失效,形成了緩存的雪崩,此時前端緩存已經沒法起到承擔部分壓力的做用,後端服務器將會承受巨大的壓力,整個系統頗有可能被壓垮,因此,咱們應該想辦法不讓這種狀況發生,可是因爲上述HASH算法自己的緣故,使用取模法進行緩存時,這種狀況是沒法避免的,爲了解決這些問題,一致性哈希算法誕生了。
咱們來回顧一下使用上述算法會出現的問題。
問題1:當緩存服務器數量發生變化時,會引發緩存的雪崩,可能會引發總體系統壓力過大而崩潰(大量緩存同一時間失效)。
問題2:當緩存服務器數量發生變化時,幾乎全部緩存的位置都會發生改變,怎樣才能儘可能減小受影響的緩存呢?
其實,上面兩個問題是一個問題,那麼,一致性哈希算法可以解決上述問題嗎?
咱們如今就來了解一下一致性哈希算法。
一致性哈希算法的基本概念
其實,一致性哈希算法也是使用取模的方法,只是,剛纔描述的取模法是對服務器的數量進行取模,而一致性哈希算法是對2^32取模,什麼意思呢?咱們慢慢聊。
首先,咱們把二的三十二次方想象成一個圓,就像鐘錶同樣,鐘錶的圓能夠理解成由60個點組成的圓,而此處咱們把這個圓想象成由2^32個點組成的圓,示意圖以下:
圓環的正上方的點表明0,0點右側的第一個點表明1,以此類推,二、三、四、五、6……直到2^32-1,也就是說0點左側的第一個點表明2^32-1
咱們把這個由2的32次方個點組成的圓環稱爲hash環。
那麼,一致性哈希算法與上圖中的圓環有什麼關係呢?咱們繼續聊,仍然以以前描述的場景爲例,假設咱們有3臺緩存服務器,服務器A、服務器B、服務器C,那麼,在生產環境中,這三臺服務器確定有本身的IP地址,咱們使用它們各自的IP地址進行哈希計算,使用哈希後的結果對2^32取模,可使用以下公式示意。
hash(服務器A的IP地址) % 2^32
經過上述公式算出的結果必定是一個0到2^32-1之間的一個整數,咱們就用算出的這個整數,表明服務器A,既然這個整數確定處於0到2^32-1之間,那麼,上圖中的hash環上一定有一個點與這個整數對應,而咱們剛纔已經說明,使用這個整數表明服務器A,那麼,服務器A就能夠映射到這個環上,用下圖示意
同理,服務器B與服務器C也能夠經過相同的方法映射到上圖中的hash環中
hash(服務器B的IP地址) % 2^32
hash(服務器C的IP地址) % 2^32
經過上述方法,能夠將服務器B與服務器C映射到上圖中的hash環上,示意圖以下
假設3臺服務器映射到hash環上之後如上圖所示(固然,這是理想的狀況,咱們慢慢聊)。
好了,到目前爲止,咱們已經把緩存服務器與hash環聯繫在了一塊兒,咱們經過上述方法,把緩存服務器映射到了hash環上,那麼使用一樣的方法,咱們也能夠將須要緩存的對象映射到hash環上。
假設,咱們須要使用緩存服務器緩存圖片,並且咱們仍然使用圖片的名稱做爲找到圖片的key,那麼咱們使用以下公式能夠將圖片映射到上圖中的hash環上。
hash(圖片名稱) % 2^32
映射後的示意圖以下,下圖中的橘黃色圓形表示圖片
好了,如今服務器與圖片都被映射到了hash環上,那麼上圖中的這個圖片到底應該被緩存到哪一臺服務器上呢?上圖中的圖片將會被緩存到服務器A上,爲何呢?由於從圖片的位置開始,沿順時針方向遇到的第一個服務器就是A服務器,因此,上圖中的圖片將會被緩存到服務器A上,以下圖所示。
沒錯,一致性哈希算法就是經過這種方法,判斷一個對象應該被緩存到哪臺服務器上的,將緩存服務器與被緩存對象都映射到hash環上之後,從被緩存對象的位置出發,沿順時針方向遇到的第一個服務器,就是當前對象將要緩存於的服務器,因爲被緩存對象與服務器hash後的值是固定的,因此,在服務器不變的狀況下,一張圖片一定會被緩存到固定的服務器上,那麼,當下次想要訪問這張圖片時,只要再次使用相同的算法進行計算,便可算出這個圖片被緩存在哪一個服務器上,直接去對應的服務器查找對應的圖片便可。
剛纔的示例只使用了一張圖片進行演示,假設有四張圖片須要緩存,示意圖以下
1號、2號圖片將會被緩存到服務器A上,3號圖片將會被緩存到服務器B上,4號圖片將會被緩存到服務器C上。
一致性哈希算法的優勢
通過上述描述,我想兄弟你應該已經明白了一致性哈希算法的原理了,可是話說回來,一致性哈希算法可以解決以前出現的問題嗎,咱們說過,若是簡單的對服務器數量進行取模,那麼當服務器數量發生變化時,會產生緩存的雪崩,從而頗有可能致使系統崩潰,那麼使用一致性哈希算法,可以避免這個問題嗎?咱們來模擬一遍,便可獲得答案。
假設,服務器B出現了故障,咱們如今須要將服務器B移除,那麼,咱們將上圖中的服務器B從hash環上移除便可,移除服務器B之後示意圖以下。
在服務器B未移除時,圖片3應該被緩存到服務器B中,但是當服務器B移除之後,按照以前描述的一致性哈希算法的規則,圖片3應該被緩存到服務器C中,由於從圖片3的位置出發,沿順時針方向遇到的第一個緩存服務器節點就是服務器C,也就是說,若是服務器B出現故障被移除時,圖片3的緩存位置會發生改變
可是,圖片4仍然會被緩存到服務器C中,圖片1與圖片2仍然會被緩存到服務器A中,這與服務器B移除以前並無任何區別,這就是一致性哈希算法的優勢,若是使用以前的hash算法,服務器數量發生改變時,全部服務器的全部緩存在同一時間失效了,而使用一致性哈希算法時,服務器的數量若是發生改變,並非全部緩存都會失效,而是隻有部分緩存會失效,前端的緩存仍然能分擔整個系統的壓力,而不至於全部壓力都在同一時間集中到後端服務器上。
這就是一致性哈希算法所體現出的優勢。
hash環的偏斜
在介紹一致性哈希的概念時,咱們理想化的將3臺服務器均勻的映射到了hash環上,以下圖所示
可是,理想很豐滿,現實很骨感,咱們想象的與實際狀況每每不同。
在實際的映射中,服務器可能會被映射成以下模樣。
聰明如你必定想到了,若是服務器被映射成上圖中的模樣,那麼被緩存的對象頗有可能大部分集中緩存在某一臺服務器上,以下圖所示。
上圖中,1號、2號、3號、4號、6號圖片均被緩存在了服務器A上,只有5號圖片被緩存在了服務器B上,服務器C上甚至沒有緩存任何圖片,若是出現上圖中的狀況,A、B、C三臺服務器並無被合理的平均的充分利用,緩存分佈的極度不均勻,並且,若是此時服務器A出現故障,那麼失效緩存的數量也將達到最大值,在極端狀況下,仍然有可能引發系統的崩潰,上圖中的狀況則被稱之爲hash環的偏斜,那麼,咱們應該怎樣防止hash環的偏斜呢?一致性hash算法中使用"虛擬節點"解決了這個問題,咱們繼續聊。
虛擬節點
話接上文,因爲咱們只有3臺服務器,當咱們把服務器映射到hash環上的時候,頗有可能出現hash環偏斜的狀況,當hash環偏斜之後,緩存每每會極度不均衡的分佈在各服務器上,聰明如你必定已經想到了,若是想要均衡的將緩存分佈到3臺服務器上,最好能讓這3臺服務器儘可能多的、均勻的出如今hash環上,可是,真實的服務器資源只有3臺,咱們怎樣憑空的讓它們多起來呢,沒錯,就是憑空的讓服務器節點多起來,既然沒有多餘的真正的物理服務器節點,咱們就只能將現有的物理節點經過虛擬的方法複製出來,這些由實際節點虛擬複製而來的節點被稱爲"虛擬節點"。加入虛擬節點之後的hash環以下。
"虛擬節點"是"實際節點"(實際的物理服務器)在hash環上的複製品,一個實際節點能夠對應多個虛擬節點。
從上圖能夠看出,A、B、C三臺服務器分別虛擬出了一個虛擬節點,固然,若是你須要,也能夠虛擬出更多的虛擬節點。引入虛擬節點的概念後,緩存的分佈就均衡多了,上圖中,1號、3號圖片被緩存在服務器A中,5號、4號圖片被緩存在服務器B中,6號、2號圖片被緩存在服務器C中,若是你還不放心,能夠虛擬出更多的虛擬節點,以便減少hash環偏斜所帶來的影響,虛擬節點越多,hash環上的節點就越多,緩存被均勻分佈的機率就越大。
前端