一致性哈希和哈希槽對比

背景

隨著memcache和redis的出現(xiàn),更多人認識到了一致性哈希。

一致性哈希用于解決分布式緩存系統(tǒng)中的數(shù)據(jù)選擇節(jié)點存儲問題和數(shù)據(jù)選擇節(jié)點讀取問題以及在增刪節(jié)點后減少數(shù)據(jù)緩存的消失范疇,防止雪崩的發(fā)生。

哈希槽是在redis cluster集群方案中采用的,redis cluster集群沒有采用一致性哈希方案,而是采用數(shù)據(jù)分片中的哈希槽來進行數(shù)據(jù)存儲與讀取的。

哈希

哈希算法最重要的特點就是:

  • 相同的輸入一定得到相同的輸出;
  • 不同的輸入大概率得到不同的輸出。

哈希碰撞

哈希碰撞是指,兩個不同的輸入得到了相同的輸出:

"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0

有童鞋會問:碰撞能不能避免?答案是不能。碰撞是一定會出現(xiàn)的,因為輸出的字節(jié)長度是固定的,String的hashCode()輸出是4字節(jié)整數(shù),最多只有4294967296種輸出,但輸入的數(shù)據(jù)長度是不固定的,有無數(shù)種輸入。所以,哈希算法是把一個無限的輸入集合映射到一個有限的輸出集合,必然會產(chǎn)生碰撞。

碰撞不可怕,我們擔心的不是碰撞,而是碰撞的概率,因為碰撞概率的高低關系到哈希算法的安全性。一個安全的哈希算法必須滿足:

  • 碰撞概率低;
  • 不能猜測輸出。

不能猜測輸出是指,輸入的任意一個bit的變化會造成輸出完全不同,這樣就很難從輸出反推輸入(只能依靠暴力窮舉)。假設一種哈希算法有如下規(guī)律:

hashA("java001") = "123456"
hashA("java002") = "123457"
hashA("java003") = "123458"

那么很容易從輸出123459反推輸入,這種哈希算法就不安全。安全的哈希算法從輸出是看不出任何規(guī)律的:

hashB("java001") = "123456"
hashB("java002") = "580271"
hashB("java003") = ???

常用的哈希算法有:

算法 輸出長度(位) 輸出長度(字節(jié))
MD5 128 bits 16 bytes
SHA-1 160 bits 20 bytes
RipeMD-160 160 bits 20 bytes
SHA-256 256 bits 32 bytes
SHA-512 512 bits 64 bytes

根據(jù)碰撞概率,哈希算法的輸出長度越長,就越難產(chǎn)生碰撞,也就越安全。

哈希算法的用途

  • 判斷是否篡改

因為相同的輸入永遠會得到相同的輸出,因此,如果輸入被修改了,得到的輸出就會不同。

我們在docker hup上隨便找一個鏡像,看到采用的hash算法為SHA-256:

image.png

如何判斷下載到本地的軟件是原始的、未經(jīng)篡改的文件?
我們只需要自己計算一下本地文件的哈希值,再與官網(wǎng)公開的哈希值對比,如果相同,說明文件下載正確,否則,說明文件已被篡改。

  • 密碼加密

在登錄系統(tǒng)中我們通常使用md5方式加密用戶密碼,這樣當數(shù)據(jù)庫泄漏之后,也看不到用戶的密碼。
因為相同的輸入永遠會得到相同的輸出,因此,對用戶輸入的密碼進行MD5加密并與數(shù)據(jù)庫存儲的MD5對比,如果一致,說明口令正確,否則,口令錯誤

介紹了這么多,言歸正傳,介紹一致性哈希。

一致性哈希

先拋出一致性哈希的目的是:為了解決分布式系統(tǒng)中擴容或者縮容節(jié)點時造成大量數(shù)據(jù)遷移的問題。

一致性hash是一個0-2^32的閉合圓,占用4個字節(jié)(擁有2^23個桶空間,每個桶里面可以存儲很多數(shù)據(jù),可以理解為s3的存儲桶)所有節(jié)點存儲的數(shù)據(jù)都是不一樣的。計算一致性哈希是采用的是如下步驟:

  1. 對節(jié)點進行hash,通常使用其節(jié)點的ip或者是具有唯一標示的數(shù)據(jù)進行hash(ip),將其值分布在這個閉合圓上。
  2. 將存儲的key進行hash(key),然后將其值要分布在這個閉合圓上。
  3. 從hash(key)在圓上映射的位置開始順時針方向找到的一個節(jié)點即為存儲key的節(jié)點。如果到圓上的0處都未找到節(jié)點,那么0位置后的順時針方向的第一個節(jié)點就是key的存儲節(jié)點。

添加節(jié)點帶來的影響
圖1為一致性hash的分布情況,箭頭指向key的分布情況。

圖1

如果現(xiàn)在node2和node4節(jié)點中間增加一個node5節(jié)點,那么在node4和node2之間的這些數(shù)據(jù)要存儲的節(jié)點就會有所變化。在圖中的黃色區(qū)域的數(shù)據(jù)將會從原來的node4節(jié)點挪到node5節(jié)點。
圖2

刪除節(jié)點帶來的影響
以圖1為基準,由于node2節(jié)點存在大量熱點數(shù)據(jù),導致node2節(jié)點不堪壓力宕機了。

這樣就產(chǎn)生一個影響:原來node2的熱點數(shù)據(jù)轉移到node4上,node4也會扛不住掛掉,掛掉之后數(shù)據(jù)又轉移給node3,如此循環(huán)會造成所有節(jié)點崩潰,也就是前面所說的雪崩的情況。


圖3

節(jié)點太少造成的影響
節(jié)點太少的話可能造成數(shù)據(jù)傾斜的情況,如圖中中只有倆節(jié)點,可能會造成大量數(shù)據(jù)存放在node A節(jié)點上,而node B節(jié)點存儲很少的數(shù)據(jù)。

圖4

所以,一致性哈希算法雖然減少了數(shù)據(jù)遷移量,但是存在節(jié)點分布不均勻的問題。

虛擬節(jié)點
要想解決節(jié)點能在哈希環(huán)上分配不均勻的問題,就是要有大量的節(jié)點,節(jié)點數(shù)越多,哈希環(huán)上的節(jié)點分布的就越均勻。

為了解決雪崩現(xiàn)象和數(shù)據(jù)傾斜現(xiàn)象,提出了虛擬節(jié)點這個概念。就是將真實節(jié)點計算多個哈希形成多個虛擬節(jié)點并放置到哈希環(huán)上,定位算法不變,只是多了一步虛擬節(jié)點到真實節(jié)點映射的過程

以雪崩現(xiàn)象來說明:如下圖節(jié)點real1節(jié)點又倆個虛擬節(jié)點v100和v101,real2有倆個虛擬節(jié)點v200和v201,real3節(jié)點有v300和v301倆個虛擬節(jié)點。


圖5

當real1節(jié)點掛掉后,v100和v101節(jié)點也會隨即消失,這時k1數(shù)據(jù)就會被分配到v301上,k4就會被分配到了v200上,這就解決了雪崩的問題,當某個節(jié)點宕機后,其數(shù)據(jù)并沒有全部分配給某一個節(jié)點,而是被分到了多個節(jié)點。


圖6

正因為加入了虛擬節(jié)點機制,數(shù)據(jù)傾斜的問題也隨之解決

注意:

  1. 真實節(jié)點不放置到哈希環(huán)上,只有虛擬節(jié)點才會放上去。
  2. 為什么要使用閉合的哈希環(huán)?舉個例子,如果在2^23-3處有一個key,而2^23-3~2^23處并沒有節(jié)點,那么這個key該存在哪里節(jié)點呢?說到這里你應該明白了吧。
  3. 一致性哈希使用的32個bits,4個bytes,肯定也會有哈希碰撞的問題存在。

哈希槽

redis cluster采用數(shù)據(jù)分片的哈希槽來進行數(shù)據(jù)存儲和數(shù)據(jù)的讀取。redis cluster一共有2^14(16384)個槽,所有的master節(jié)點都會有一個槽區(qū)比如0~1000,槽數(shù)是可以遷移的。master節(jié)點的slave節(jié)點不分配槽,只擁有讀權限。但是注意在代碼中redis cluster執(zhí)行讀寫操作的都是master節(jié)點,并不是你想 的讀是從節(jié)點,寫是主節(jié)點。第一次新建redis cluster時,16384個槽是被master節(jié)點均勻分布的。

WechatIMG6.jpeg

和一致性哈希相比

  1. 它并不是閉合的,key的定位規(guī)則是根據(jù)CRC-16(key)%16384的值來判斷屬于哪個槽區(qū),從而判斷該key屬于哪個節(jié)點,而一致性哈希是根據(jù)hash(key)的值來順時針找第一個hash(ip)的節(jié)點,從而確定key存儲在哪個節(jié)點。
  2. 一致性哈希是創(chuàng)建虛擬節(jié)點來實現(xiàn)節(jié)點宕機后的數(shù)據(jù)轉移并保證數(shù)據(jù)的安全性和集群的可用性的。redis cluster是采用master節(jié)點有多個slave節(jié)點機制來保證數(shù)據(jù)的完整性的,master節(jié)點寫入數(shù)據(jù),slave節(jié)點同步數(shù)據(jù)。當master節(jié)點掛機后,slave節(jié)點會通過選舉機制選舉出一個節(jié)點變成master節(jié)點,實現(xiàn)高可用。但是這里有一點需要考慮,如果master節(jié)點存在熱點緩存,某一個時刻某個key的訪問急劇增高,這時該mater節(jié)點可能操勞過度而死,隨后從節(jié)點選舉為主節(jié)點后,同樣宕機,一次類推,造成緩存雪崩。解決這個問題請看我的另一篇文章如何應對熱點緩存問題
  3. 擴容和縮容
    可以看到一致性哈希算法在新增和刪除節(jié)點后,數(shù)據(jù)會按照順時針來重新分布節(jié)點。而redis cluster的新增和刪除節(jié)點都需要手動來分配槽區(qū)。
  • 新建master節(jié)點
    使用redis-trib.rb工具來創(chuàng)建master節(jié)點

    ./redis-trib.rb add-node 172.60.0.7:6379 172.60.0.5:6379
    注釋:

    192.168.10.219:6378是新增的節(jié)點

    192.168.10.219:6379集群任一個舊節(jié)點
    注意:新建的master節(jié)點是沒有槽區(qū)的,需要給master節(jié)點分配槽,不然緩存無法命中。分配槽的方法自行百度。
    刪除master節(jié)點
    1.如果主節(jié)點有從節(jié)點,需要將從節(jié)點轉移到別的主節(jié)點上。
    2.轉移后 如果主節(jié)點有哈希槽,將哈希槽轉移到別的master節(jié)點上,然后在刪除master節(jié)點
    注意:redis cluster的動態(tài)擴容和縮容并不會影響集群的使用。

總結

一致性哈希算法旨在確保數(shù)據(jù)均勻分布在一組服務器或節(jié)點上,以實現(xiàn)負載均衡。它的目標是在節(jié)點的添加刪除時,盡量減少數(shù)據(jù)的遷移

參考文章
一致性哈希原理
一致性hash介紹及使用原理

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • redis集群分為服務端集群和客戶端分片,redis3.0以上版本實現(xiàn)了集群機制,即服務端集群,3.0以下使用客戶...
    hadoop_null閱讀 1,604評論 0 6
  • NOSQL類型簡介鍵值對:會使用到一個哈希表,表中有一個特定的鍵和一個指針指向特定的數(shù)據(jù),如redis,volde...
    MicoCube閱讀 4,057評論 2 27
  • 1.1 Redis集群的設計原則和初衷 在官方文檔Cluster Spec中,作者詳細介紹了Redis集群為什么要...
    Flame_1109閱讀 2,169評論 1 5
  • 1 Redis介紹1.1 什么是NoSql為了解決高并發(fā)、高可擴展、高可用、大數(shù)據(jù)存儲問題而產(chǎn)生的數(shù)據(jù)庫解決方...
    克魯?shù)吕?/span>閱讀 5,354評論 0 36
  • 2月17日/成功的背后隱藏著無數(shù)次的失敗,在創(chuàng)新上來說,要想成功也總是伴隨著無數(shù)次的失敗,無論是技術還是理論,都要...
    如釋筆記閱讀 1,329評論 0 0