背景
隨著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:
如何判斷下載到本地的軟件是原始的、未經(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ù)都是不一樣的。計算一致性哈希是采用的是如下步驟:
- 對節(jié)點進行hash,通常使用其節(jié)點的ip或者是具有唯一標示的數(shù)據(jù)進行hash(ip),將其值分布在這個閉合圓上。
- 將存儲的key進行hash(key),然后將其值要分布在這個閉合圓上。
- 從hash(key)在圓上映射的位置開始順時針方向找到的一個節(jié)點即為存儲key的節(jié)點。如果到圓上的0處都未找到節(jié)點,那么0位置后的順時針方向的第一個節(jié)點就是key的存儲節(jié)點。
添加節(jié)點帶來的影響
圖1為一致性hash的分布情況,箭頭指向key的分布情況。
如果現(xiàn)在node2和node4節(jié)點中間增加一個node5節(jié)點,那么在node4和node2之間的這些數(shù)據(jù)要存儲的節(jié)點就會有所變化。在圖中的黃色區(qū)域的數(shù)據(jù)將會從原來的node4節(jié)點挪到node5節(jié)點。
刪除節(jié)點帶來的影響
以圖1為基準,由于node2節(jié)點存在大量熱點數(shù)據(jù),導致node2節(jié)點不堪壓力宕機了。
這樣就產(chǎn)生一個影響:原來node2的熱點數(shù)據(jù)轉移到node4上,node4也會扛不住掛掉,掛掉之后數(shù)據(jù)又轉移給node3,如此循環(huán)會造成所有節(jié)點崩潰,也就是前面所說的雪崩的情況。
節(jié)點太少造成的影響
節(jié)點太少的話可能造成數(shù)據(jù)傾斜的情況,如圖中中只有倆節(jié)點,可能會造成大量數(shù)據(jù)存放在node A節(jié)點上,而node B節(jié)點存儲很少的數(shù)據(jù)。
所以,一致性哈希算法雖然減少了數(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é)點。
當real1節(jié)點掛掉后,v100和v101節(jié)點也會隨即消失,這時k1數(shù)據(jù)就會被分配到v301上,k4就會被分配到了v200上,這就解決了雪崩的問題,當某個節(jié)點宕機后,其數(shù)據(jù)并沒有全部分配給某一個節(jié)點,而是被分到了多個節(jié)點。
正因為加入了虛擬節(jié)點機制,數(shù)據(jù)傾斜的問題也隨之解決
注意:
- 真實節(jié)點不放置到哈希環(huán)上,只有虛擬節(jié)點才會放上去。
- 為什么要使用閉合的哈希環(huán)?舉個例子,如果在2^23-3處有一個key,而2^23-3~2^23處并沒有節(jié)點,那么這個key該存在哪里節(jié)點呢?說到這里你應該明白了吧。
- 一致性哈希使用的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é)點均勻分布的。
和一致性哈希相比
- 它并不是閉合的,key的定位規(guī)則是根據(jù)CRC-16(key)%16384的值來判斷屬于哪個槽區(qū),從而判斷該key屬于哪個節(jié)點,而一致性哈希是根據(jù)hash(key)的值來順時針找第一個hash(ip)的節(jié)點,從而確定key存儲在哪個節(jié)點。
- 一致性哈希是創(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é)點后,同樣宕機,一次類推,造成緩存雪崩。解決這個問題請看我的另一篇文章如何應對熱點緩存問題
- 擴容和縮容
可以看到一致性哈希算法在新增和刪除節(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介紹及使用原理