在分散式叢集中,對機器的新增刪除,或者機器故障後自動脫離叢集這些操作是分散式叢集管理最基本的功能。如果採用常用的hash(object)%N算
法,那麼在有機器新增或者刪除後,很多原有的資料就無法找到了,這樣嚴重的違反了單調性原則。接下來主要講解一下一致性雜湊演算法是如何設計的:
按照常用的hash演算法來將對應的key雜湊到一個具有2^32次方個桶的空間中,即0~(2^32)-1的數字空間中。現在我們可以將這些數字頭尾相連,想象成一個閉合的環形。如下圖
現在我們將object1、object2、object3、object4四個物件透過特定的Hash函式計算出對應的key值,然後雜湊到Hash環上。如下圖:
Hash(object1) = key1;
Hash(object2) = key2;
Hash(object3) = key3;
Hash(object4) = key4;
在採用一致性雜湊演算法的分散式叢集中將新的機器加入,其原理是透過使用與物件儲存一樣的Hash演算法將機器也對映到環中(一般情況下對機器的hash計算是採用機器的IP或者機器唯一的別名作為輸入值),然後以順時針的方向計算,將所有物件儲存到離自己最近的機器中。
假設現在有NODE1,NODE2,NODE3三臺機器,透過Hash演算法得到對應的KEY值,對映到環中,其示意圖如下:
Hash(NODE1) = KEY1;
Hash(NODE2) = KEY2;
Hash(NODE3) = KEY3;
通
過上圖可以看出物件與機器處於同一雜湊空間中,這樣按順時針轉動object1儲存到了NODE1中,object3儲存到了NODE2
中,object2、object4儲存到了NODE3中。在這樣的部署環境中,hash環是不會變更的,因此,透過算出物件的hash值就能快速的定位
到對應的機器中,這樣就能找到物件真正的儲存位置了。
普通hash求餘演算法最為不妥的地方就是在有機器的新增或者刪除之後會照成大量的物件儲存位置失效,這樣就大大的不滿足單調性了。下面來分析一下一致性雜湊演算法是如何處理的。
1. 節點(機器)的刪除
以上面的分佈為例,如果NODE2出現故障被刪除了,那麼按照順時針遷移的方法,object3將會被遷移到NODE3中,這樣僅僅是object3的對映位置發生了變化,其它的物件沒有任何的改動。如下圖:
2. 節點(機器)的新增
如果往叢集中新增一個新的節點NODE4,透過對應的雜湊演算法得到KEY4,並對映到環中,如下圖:
透過按順時針遷移的規則,那麼object2被遷移到了NODE4中,其它物件還保持這原有的儲存位置。透過對節點的新增和刪除的分析,一致性雜湊演算法在
保持了單調性的同時,還是資料的遷移達到了最小,這樣的演算法對分散式叢集來說是非常合適的,避免了大量資料遷移,減小了伺服器的的壓力。
根
據上面的圖解分析,一致性雜湊演算法滿足了單調性和負載均衡的特性以及一般hash演算法的分散性,但這還並不能當做其被廣泛應用的原由,因為還缺少了平衡
性。下面將分析一致性雜湊演算法是如何滿足平衡性的。hash演算法是不保證平衡的,如上面只部署了NODE1和NODE3的情況(NODE2被刪除的
圖),object1儲存到了NODE1中,而object2、object3、object4都儲存到了NODE3中,這樣就照成了非常不平衡的狀態。
在一致性雜湊演算法中,為了儘可能的滿足平衡性,其引入了虛擬節點。
——“虛擬節點”( virtual node )是實際節點(機器)在 hash 空間的複製品( replica ),一實際個節點(機器)對應了若干個“虛擬節點”,這個對應個數也成為“複製個數”,“虛擬節點”在 hash 空間中以hash值排列。
以上面只部署了NODE1和NODE3的情況(NODE2被刪除的圖)為例,之前的物件在機器上的分佈很不均衡,現在我們以2個副本(複製個數)為例,這樣整個hash環中就存在了4個虛擬節點,最後物件對映的關係圖如下:
根
據上圖可知物件的對映關
系:object1->NODE1-1,object2->NODE1-2,object3->NODE3-2,object4->NODE3-1。
透過虛擬節點的引入,物件的分佈就比較均衡了。那麼在實際操作中,正真的物件查詢是如何工作的呢?物件從hash到虛擬節點到實際節點的轉換如下圖:
“虛擬節點”的hash計算可以採用對應節點的IP地址加數字字尾的方式。例如假設NODE1的IP地址為192.168.1.100。引入“虛擬節點”前,計算 cache A 的 hash 值:
Hash(“192.168.1.100”);
引入“虛擬節點”後,計算“虛擬節”點NODE1-1和NODE1-2的hash值:
Hash(“192.168.1.100#1”); // NODE1-1
Hash(“192.168.1.100#2”); // NODE1-2