Consistent Hashing 一致性hash演算法
典型的應用場景是: 有N臺伺服器提供快取服務,需要對伺服器進行負載均衡,將請求平均分發到每臺伺服器上,每臺機器負責1/N的服務。
常用的演算法是對hash結果取餘數 (hash() mod N
):對機器編號從0到N-1,按照自定義的 hash()演算法,對每個請求的hash()值按N取模,得到餘數i,然後將請求分發到編號為i的機器。但這樣的演算法方法存在致命問題,如果某一臺機器宕 機,那麼應該落在該機器的請求就無法得到正確的處理,這時需要將當掉的伺服器從演算法從去除,此時候會有(N-1)/N的伺服器的快取資料需要重新進行計 算;如果新增一臺機器,會有N /(N+1)的伺服器的快取資料需要進行重新計算。對於系統而言,這通常是不可接受的顛簸(因為這意味著大量快取的失效或者資料需要轉移)。那麼,如何設 計一個負載均衡策略,使得受到影響的請求儘可能的少呢?
在Memcached、
1、Consistent Hashing演算法描述
下面以Memcached中的Consisten Hashing演算法為例說明(參考memcached的分散式演算法)。
由於hash演算法結果一般為unsigned int型,因此對於hash函式的結果應該均勻分佈在[0,232-1]間,如果我們把一個圓環用232
用同樣的hash(key)函式求出需要儲存資料的鍵的雜湊值,並對映到圓上。然後從資料對映到的位置開始順時針查詢,將資料儲存到找到的第一個伺服器(節點)上。
新增一個節點的時候,只有在圓環上新增節點逆時針方向的第一個節點的資料會受到影響。刪除一個節點的時候,只有在圓環上原來刪除節點順時針方向的第一個節 點的資料會受到影響,因此通過Consistent Hashing很好地解決了負載均衡中由於新增節點、刪除節點引起的hash值顛簸問題。
虛擬節點(virtual nodes):之所以要引進虛擬節點是因為在伺服器(節點)數較少的情況下 (例如只有3臺伺服器),通過hash(key)算出節點的雜湊值在圓環上並不是均勻分佈的(稀疏的),仍然會出現各節點負載不均衡的問題。虛擬節點可以 認為是實際節點的複製品(replicas),本質上與實際節點實際上是一樣的(key並不相同)。引入虛擬節點後,通過將每個實際的伺服器(節點)數按 照一定的比例(例如200倍)擴大後並計算其hash(key)值以均勻分佈到圓環上。在進行負載均衡時候,落到虛擬節點的雜湊值實際就落到了實際的節點 上。由於所有的實際節點是按照相同的比例複製成虛擬節點的,因此解決了節點數較少的情況下雜湊值在圓環上均勻分佈的問題。
虛擬節點對Consistent Hashing結果的影響
從上圖可以看出,在節點數為10個的情況下,每個實際節點的虛擬節點數為實際節點的100-200倍的時候,結果還是很均衡的。
2、Consistent Hashing演算法實現:
文章Consistent Hashing中描述了Consistent Hashing的Java實現,很簡潔。
import java.util.Collection; import java.util.SortedMap; import java.util.TreeMap; public class ConsistentHash<T> { private final HashFunction hashFunction; private final int numberOfReplicas; private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>(); public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, Collection<T> nodes) { this.hashFunction = hashFunction; this.numberOfReplicas = numberOfReplicas; for (T node : nodes) { add(node); } } public void add(T node) { for (int i = 0; i < numberOfReplicas; i++) { circle.put(hashFunction.hash(node.toString() + i), node); } } public void remove(T node) { for (int i = 0; i < numberOfReplicas; i++) { circle.remove(hashFunction.hash(node.toString() + i)); } } public T get(Object key) { if (circle.isEmpty()) { return null; } int hash = hashFunction.hash(key); if (!circle.containsKey(hash)) { SortedMap<Integer, T> tailMap = circle.tailMap(hash); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } return circle.get(hash); } }
文章Consistent hashing implemented simply in Python描述了Consistent Hashing演算法的python 實現
3、參考文件
http://weblogs.java.net/blog/2007/11/27/consistent-hashing
http://michaelnielsen.org/blog/consistent-hashing/
http://www.spiteful.com/2008/03/17/programmers-toolbox-part-3-consistent-hashing/
http://tech.idv2.com/2008/07/24/memcached-004/
http://amix.dk/blog/viewEntry/19367
http://amix.dk/blog/viewEntry/19369
http://www.javaworld.com/javaworld/jw-10-2008/jw-10-load-balancing-1.html