Redis中的一致性雜湊問題

大資料學習與分享發表於2020-11-06

在說redis中的雜湊(準確來說是一致性雜湊)問題之前,先來看一個問題:為什麼在分散式叢集中一致性雜湊會得到大量應用?

在一個分散式系統中,要將資料儲存到具體某個節點,或者將來自客戶端的請求分配到某個伺服器節點做負載均衡,如果採用普通的hash取模演算法進行對映,即如key.hashCode()%N,key代表資料的key,N是伺服器節點數,使用上能達到預期效果。

但是如果此時要下線一個伺服器或者上線一個新的伺服器,那麼原來的對映將全部失效。如果是做分散式儲存,則需要做資料遷移;如果是做分散式快取,則原來的快取失效,需要讓新增或下線的節點生效就需要做rehash,資料較大會比較消耗時間(其實這點對HashMap瞭解的,很熟悉這一點,HashMap在動態擴容進行rehash,資料量過大時很消耗時間影響效能)。這時,一致性雜湊就派上用場了。

下面通過幾個問題逐步介紹redis2.X和redis3.X中的一些特性,來了解一致性雜湊在redis中的應用,以及遇到的問題,不同版本是如何解決的。

1. 假如有兩臺redis伺服器,jedis客戶端要存入資料到這兩臺伺服器,它如何知道要存入哪臺伺服器?

這個就是開篇所說一般的做法:雜湊取模。
key.hashcode() % nums(key是redis中的key,nums是redis伺服器數)最終結果範圍:0到nums-1

2. 此時新增一臺redis伺服器,資料能寫入到新增的機器上嗎?不能。還是對原有redis伺服器數進行取模。

那麼如何解決這一問題呢?nums不定義為redis伺服器具體數,而是一個比較大的值:2^32,從而對映到一個比較大的空間內,拿key.hashcode*()% 2^32-1來確定存入的伺服器。最終會形成一個一致性雜湊環,沿著這個環往下找,直至找到。

當然這裡key.hashcode*()% 2^32-1只是舉個例子,實際生產中我們會採用雜湊演算法,如MD5、MurMurHash、crc32將資料對映到一個雜湊環上。

3. 假如在新增一臺redis伺服器C前,資料存在節點A。加入C後,客戶端在操作的時候,會出現什麼問題?

查詢資料時,如果通過一致性雜湊演算法得出資料在C上,但真實資料在A上,客戶端在C上查詢會找不到資料就會報空指標異常。
這個其實是在redis2.X中的問題,因為redis2.X不支援冬天擴容。這時我們可以考慮找一個合適的時間點如業務峰值低的時候,將環中的所有資料載入出來,灌入到另外一個新增節點後的環中進行處理。

4. redis3.X如何解決redis2.X的上述問題?

通過上面的問題可以得知redis2.X不支援動態加節點,就算成功加入新節點,資料會發生錯亂現象,而redis3.X解決了這個問題:

redis叢集內建了16384個雜湊槽,當需要在叢集中插入資料時,先對key使用crc16演算法得出一個結果,然後把結果對16384求餘數。這樣每個key都會對應一個編號在0~16383之間的雜湊槽,redis會根據節點數量大致均等的將雜湊槽對映到不同節點。

redis叢集的每個節點負責一部分雜湊槽,這種結構很容易新增或者刪除節點,並且無論是新增刪除或者修改某一個節點,都不會造成叢集不可用的狀態。雜湊槽的好處在於可以方便的新增或移除節點:

1)當需要增加節點時,只需要把其他節點的某些雜湊槽挪到新節點就可以了

2)當需要移除節點時,只需要把移除節點上的雜湊槽挪到其他節點就行了

5. redis3.X的hash碰撞問題

通過hash對映,當某臺機器上資料過多支撐不住導致當機,此時它負責的資料會分配到其他機器,而redis叢集伺服器配置一般相同,其他機器也扛不住,就會造成雪崩,即便有主備也解決不了,最終可能導致整個叢集都會掛掉。下圖演示了節點C當機,C上的資料對映到D上的示例:

這其實就是分散式系統中極其常見的問題,資料傾斜。可以考慮通過如下方式解決:

1)如給大量相似資料即key相同,給key加上隨機串,將key打散盡可能隨機分配,避免資料傾斜
2)參考redis2.X版本中"虛擬節點"的做法,為每個真實節點引入N個虛擬節點。具體看下文

6. redis2.X是如何解決hash碰撞的問題?redis2.X有一個非常重要的概念:虛擬節點,每個節點都虛擬出160個虛擬節點。資料的儲存是沿著環的順時針方向找一個虛擬節點,每個虛擬節點都會關聯到一個真實節點。

圖中的A1、A2、B1、B2、C1、C2、D1、D2都是虛擬節點,機器A負載A1、A2的資料,機器B負載B1、B2的資料,機器C負載C1、C2的資料。由於這些虛擬節點數量很多,均勻分佈,因此不會造成"雪崩"現象。


關注微信公眾號:大資料學習與分享,獲取更對技術乾貨

相關文章