Redis為什么使用哈希槽而不用一致性哈希
今天我們聊個知識點為什么Redis使用哈希槽而不是一致性哈希。
先看文章大綱,提前了解本期內容
圖片
往期回顧
之前小許用圖文并茂的方式用一期內容讓大家快速了解了一致性哈希算法,看過的朋友應該還有印象,沒看過的朋友可以點擊這里看一遍《五分鐘了解一致性哈希算法》。
看明白這篇一致性哈希算法基礎,會對本期內容有更好的認識和對比性。
這里我們再簡單回顧下:
一致性哈希算法就很好地解決了分布式系統在擴容或者縮容時,發生過多的數據遷移的問題。
算法是對 2^32 進行取模運算的結果值虛擬成一個圓環,環上的刻度對應一個 0~2^32 - 1 之間的數值。
通過虛擬節點的方式很好的處理了數據不平衡問題。
圖片
不同的計算方式
不知道朋友們記不記得Redis Cluster的實現,也是用了Hash的方式將鍵值按照一定算法分配到各個節點的,但是卻沒有使用一致性哈希算法,而是引入了哈希槽的概念!
這是為什么呢?????
我們先看下一致性哈希和哈希槽在計算上的區別
圖片
圖中A、B、C表示的是三個節點,k1和k2表示的是key:
?? 一致性哈希是經過 hash() 函數計算后對 2^32 取模的值虛擬成一個圓環
?? 哈希槽是將每個key通過CRC16計算得到一個16bit的值,然后16bit值再對16384取模來決定放置哪個槽
雖說在計算方式上有區別,好像都解決了數據均衡的問題,應該都是不錯的選擇。
OK,本文將先對Redis集群節點增減時如何進行哈希槽的分配進行分享,再回過頭看為什么Redis 集群沒有使用一致性hash,而是引入了哈希槽的概念的原因究竟是什么!
Redis Cluster集群
Redis集群是一種分布式數據庫方案,通過服務器分片技術進行數據管理,我們來對它進行一個歸納總結。
哈希槽
集群將數據劃分為 16384 (2^14)個槽位(哈希槽),每個Redis服務節點分配了一部分槽位,因為槽位的信息存儲于每個節點中,客戶端請求的key通過CRC16校驗后對16384取模來決定放置哪個槽,這樣也就定位到指定的節點中。
圖片
上圖中 key 【小許】和【code】經過 CRC16 計算后再對哈希槽總個數 16384 取模,得到哈希槽位置分別是在888的節點A上和10924的節點C上面。
?? 重點:每個節點都會記錄哪些槽分配給了自己,哪些槽被分配給了其他節點
增加節點
新增一個節點D,redis cluster的這種做法是從各個節點的前面各拿取一部分slot(槽)到D上,會變成這樣:
圖片
此時服務A、B、C、D通過分配各自有了對應的哈希槽,新增節點后集群會自動進行哈希槽的重新平均分配,比如上圖中四個節點中每個節點的槽位數是:18384 / 4 = 4096。
當然這個你使用命令 【cluster addslots】為每個節點自定義分配槽的數量,這里有個特點,如果我們節點的機器性能有差異,那就可以為性能好的,配置更多槽位,更好的利用機器性能。
減少節點
如果減少一個節點C,redis cluster同樣會自動進行槽數量的重新計算分配,然后后變成下面樣子:
圖片
刪除節點C之后,此時服務A、B節點中每個節點的槽位數是:18384 / 2 = 8192
客戶端訪問節點數據
Redis cluster的主節點各自負責一部分槽,我們來看下來自客戶端的請求的key是如何定位到具體的節點,然后返回對應的數據的。
圖片
來自Redis-Cli客戶端的請求連接到的是集群中的任何一個節點
- 1. 首先檢查當前key是否存在集群中的節點
- ? 通過CRC16(key)/ 16384計算出slot
- ? 查詢負責該slot負責的節點是否存在
- 1. 在該節點的話就直接就直接返回key對應的結果
- 2. 不在該節點的話,那么會 MOVED重定向(包含槽位和目標地址)指引客戶端轉向至正確的節點,并再次發送之前執行的命令
???? 相信你也和小許一樣覺得這種方式弊端很明顯,每次執行命令前都可能現在Redis節點上進行MOVED重定向才能找到要執行命令的節點,額外增加了IO開銷。
?? 不過大多數開發語言的Redis客戶端都采用 Smart客戶端 支持集群協議,讓整個訪問就更高效。
我們來看下是如何實現的!
smart客戶端
開發語言寫的Redis客戶端都會采用Smart客戶端來支持訪問集群。
主要是在內部維護哈希槽--節點的映射關系,這樣就可以在Smart客戶端實現鍵到節點的查找,避免了再進行MOVED重定向。
不過第一步還是初始化時會選擇一個運行節點,初始化槽和節點映射關系。
我們看下圖:
圖片
上面我們簡單講了下Redis-Cluster中哈希槽和增刪節點槽位的轉移分配,回歸正題。
?? 為什么Redis是使用哈希槽而不是一致性哈希呢?
有人可能會說是當節點太少時,一致性哈希容易數據分布不均勻更容易導致雪崩。
但是看過我開頭分享的一致性哈希文章,通過引入虛擬節點是基本可以避免這個問題的
如果非要說極限情況,那么Redis哈希槽,也有可能某些hash 區間的值特別多,然后導致該節點導訪問過于集中的問題。
拋開這些極端情況,通過上面對哈希槽的總結,以下這些是更值得信服的回答:
- ? 當發生擴容時候,Redis可配置映射表的方式讓哈希槽更靈活,可更方便組織映射到新增server上面的slot數,比一致性hash的算法更靈活方便。
- ? 在數據遷移時,一致性hash 需要重新計算key在新增節點的數據,然后遷移這部分數據,哈希槽則直接將一個slot對應的數據全部遷移,實現更簡單
- ? 可以靈活的分配槽位,比如性能更好的節點分配更多槽位,性能相對較差的節點可以分配較少的槽位
為什么Redis Cluster哈希槽數量是16384?
我們知道一致性哈希算法是對2的32次方取模,而哈希槽是對2的14次方取模
?? Redis作者認為這樣做不太值得;并且一般情況下一個redis集群不會有超過1000個master節點,所以16k的槽位是個比較合適的選擇。
Redis作者的回答在這里:why redis-cluster use 16384 slots? · Issue #2576 · redis/redis
圖片
總結起來主要有以下因素
- ? Redis節點間通信時,心跳包會攜帶節點的所有槽信息,它能以冪等方式來更新配置。如果采用 16384 個插槽,占空間 2KB (16384/8);如果采用 65536 個插槽,占空間 8KB (65536/8)。
- ? Redis Cluster 不太可能擴展到超過 1000 個主節點,太多可能導致網絡擁堵。
- ? 16384 個插槽范圍比較合適,當集群擴展到1000個節點時,也能確保每個master節點有足夠的插槽
這也就是為什么哈希槽的數量是16384了!