1. 程式人生 > >Redis分布式算法 — Consistent hashing(一致性哈希)

Redis分布式算法 — Consistent hashing(一致性哈希)

redis nosql 分布式 Consistent hashing 一致性哈希

傳統的分布式算法

在了解redis分布式算法之前,最好先了解一下緩存中的一個應用場景,了解了這個應用場景之後,再來理解一致性哈希算法,就容易多了,也更能體現出一致性哈希算法的優點,那麽,我們先來描述一下這個經典的分布式緩存的應用場景。

場景描述:

假設,我們有三臺緩存服務器,用於緩存圖片,我們為這三臺緩存服務器編號為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:當緩存服務器數量發生變化時,幾乎所有緩存的位置都會發生改變,怎樣才能盡量減少受影響的緩存呢?

其實,上面兩個問題是一個問題,那麽,一致性哈希算法能夠解決上述問題嗎?我們現在就來了解一下一致性哈希算法。


redis分布式算法

redis使用的是 Consistent hashing 算法:

  • Consistent hashing 是一致性hash算法
  • Consistent hashing 算法早在1997年就在論文《Consistent hashing and random trees》中提出

這個算法有一個環形hash空間的概念,我們先來了解一下環形hash空間:

  • 通常hash算法都是將value映射在一個32位的key值當中,那麽把數軸首尾相接就會形成一個圓形,取值範圍為0 ~ 2^32-1,這個圓形就是環形hash空間。如下圖:
    技術分享圖片

我們來看看如何把對象映射到環形hash空間:

  • 只考慮4個對象Object1 ~ Object4
  • 首先通過hash函數計算出這四個對象的hash值key,這些對象的hash值肯定是會落在上述中的環形hash空間範圍上的,對象的hash對應到環形hash空間上的哪一個key值那麽該對象就映射到那個位置上,這樣對象就映射到環形hash空間上了。如下圖:
    技術分享圖片

然後就是把cache映射到環形hash空間,cache就是我們的redis服務器:

  • 基本思想就是將對象和cache都映射到同一個hash環形空間中,並且使用相同的hash算法進行計算,用偽碼表示如下:
hash(cache A) = key A;
... ...
hash(cache C) = key C;

計算出cache的hash值之後,就和對象一樣映射到hash環形空間中對應的key上,使用圖片表示如下:
技術分享圖片

可以看到,Cache和Obejct都映射到這個環形hash空間中了,那麽接下來要考慮的就是如何將object映射到cache中。其實在這個環形hash空間進行一個順時針的計算即可,例如key1順時針遇到的第一個cache是cacheA,所以就將key1映射到cacheA中,key2順時針遇到的第一個cache是cacheC,那麽就將key2映射到cacheC中,以此類推。如下圖:
技術分享圖片

如果某一個cache被移除之後,那麽object會繼續順時針尋找下一個cache進行映射。例如,cacheB被移除了,映射在cacheB中的object4就會順時針往下找到cacheC,然後映射到cacheC上。如下圖:
技術分享圖片

所以當移除一個cacheB時所影響的object範圍就是cacheB與cacheA之間的那一段範圍,這個範圍是比較小的。如下圖所標出的範圍:
技術分享圖片

而當增加一個cache節點時也是同理,例如,在acheC和cacheB之間增加了一個cacheD節點,那麽object2在順時針遇到的第一個cache就是cacheD,此時就會將obejct2映射到cacheD中。如下圖:
技術分享圖片

同樣的,增加cache節點所影響的範圍也就是cacheD和cacheB之間的那一段範圍。如下圖所標出的範圍:
技術分享圖片


以上我們所講解的都是理想中的情況,我們假設了所有的cache節點都是在環形hash空間上分布均勻的。但是我們都知道到理想很豐滿現實很骨感,就像賣家秀和買家秀一種,總是會出現與實物不符的情況。
技術分享圖片

所以就有可能會出現cache節點無法均勻的分部在環形hash空間上。如下圖:
技術分享圖片

可以看到,A、B、C節點都擠在了一塊,按順時針來計算,就會有大量的數據(object)映射到A節點上,從上圖中來看就會有一大半的數據都映射到A節點上,那麽A節點所承載的數據壓力會十分大,B、C節點則無法得到很好的利用,幾乎等同閑著沒事幹。這就是Hash傾斜性所導致的現象,無法保證在環形hash空間上絕對的分布均勻。

為了解決Hash傾斜性的問題,redis引入了虛擬節點的概念,虛擬節點相當於是實際節點的一個影子或者說分身,而且虛擬節點一般都比實際節點的數量要多。引入虛擬節點後,object不再直接映射到實際的cache節點中,而是先映射到虛擬節點中。然後虛擬節點會再進行一個hash計算,最後才映射到實際的cache節點中。所以虛擬節點就是對我們的實際節點進行一個放大,如下圖:
技術分享圖片

放到環形hash空間上進行表示,就是這樣的,淺色為虛擬節點,深色為實際節點:
技術分享圖片

如上可以看到,這下分布就均勻了。這裏只是作為演示,實際情況中的節點會更多,虛擬節點和實際節點是存在一定比例的。而且隨著實際節點的增加,環形hash空間上的分布就會越來越均勻,當移除或增加cache時所受到的影響就會越小。

Consistent hashing 命中率計算公式:

(1 - n / (n + m)) * 100%

n = 現有的節點數量
m = 新增的節點數量

Redis分布式算法 — Consistent hashing(一致性哈希)