起源
在1997年由麻省理工學院提出的一種分佈式哈希(DHT)實現算法,設計目標是爲了解決因特網中的熱點(Hot spot)問題
性質
- 平衡性
- 單調性
- 分散性
- 負載
- 平滑性
考慮因素
- 數據的均衡性:計算 Hash
- 擴容:減少數據遷移,避免數據不平衡
- 宕機:減少數據遷移,避免數據不平衡
算法實現
參考《大型網站高性能架構》6.3
1、hash 取餘的問題:
1.1. key 計算 hash 取餘,找到節點
優點:數據均衡
缺點:擴容導致 N/(N+1) 節點緩存失效,需要數據遷移
2、一致性hash
2.1、長度 N 的 Hash 環(使用二叉樹實現)
2.2、節點計算 Hash
2.3、key計算 Hash 順時針找到對應的節點
優點:解決了擴容導致的緩存失效。增加節點失效比例由 N/(N+1) 變爲 1/(N+1)
缺點:數據不均衡
3、基於虛擬節點的一致性 Hash
3.1、長度 N 的 Hash 環(使用二叉樹實現)
3.2、每個服務器分配 M 個虛擬機節點
3.2、虛擬節點分佈到 Hash 環上
3.3、key計算 Hash 順時針找到對應的虛擬節點
3.4、通過虛擬節點找到物理節點
優點:解決了擴容導致的數據不均衡問題。各個階段都分擔幾乎相同的失效數據
應用場景
減少擴容時數據的遷移
- 分佈式存儲
- 分佈式緩存
一致性 Hash 代碼示例
要點
- hash 函數:MD5,crc32,crc64 等
- 存儲hash環的數據結構:TreeMap,平衡二叉樹
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* @author liuwenxue
* @date 2020-06-18
*/
public class ConsistentHash<T extends Node> {
// 節點 key:虛擬節點
private final SortedMap<Long, VirtualNode<T>> ring = new TreeMap<>();
private final HashFunction hashFunction;
public ConsistentHash(Collection<T> pNodes, int vNodeCount) {
this(pNodes,vNodeCount, new MD5Hash());
}
/**
* 虛擬節點
*
* @param pNodes 物理節點
* @param vNodeCount 虛擬節點數量
* @param hashFunction 函數函數
*/
public ConsistentHash(Collection<T> pNodes, int vNodeCount, HashFunction hashFunction) {
if (hashFunction == null) {
throw new NullPointerException("Hash Function is null");
}
this.hashFunction = hashFunction;
if (pNodes != null) {
for (T pNode : pNodes) {
addNode(pNode, vNodeCount);
}
}
}
/**
* 將 pNode 加入 ring
*
* @param pNode 物理節點
* @param vNodeCount 虛擬節點數量
*/
public void addNode(T pNode, int vNodeCount) {
if (vNodeCount < 0) {
throw new IllegalArgumentException("illegal virtual node counts :" + vNodeCount);
}
int existingReplicas = getExistingReplicas(pNode);
for (int i = 0; i < vNodeCount; i++) {
VirtualNode<T> vNode = new VirtualNode<>(pNode, i + existingReplicas);
ring.put(hashFunction.hash(vNode.getKey()), vNode);
}
}
/**
* 從 ring 中刪除物理機節點
*
* @param pNode 物理節點
*/
public void removeNode(T pNode) {
Iterator<Long> it = ring.keySet().iterator();
while (it.hasNext()) {
Long key = it.next();
VirtualNode<T> virtualNode = ring.get(key);
if (virtualNode.isVirtualNodeOf(pNode)) {
it.remove();
}
}
}
/**
* 找到對象 Key 對應的物理機節點
*
* @param objectKey 對象的 key
* @return 對應的物理節點
*/
public T routeNode(String objectKey) {
if (ring.isEmpty()) {
return null;
}
Long hashVal = hashFunction.hash(objectKey);
if (!ring.containsKey(objectKey)) {
SortedMap<Long,VirtualNode<T>> tailMap = ring.tailMap(hashVal);
hashVal = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey() ;
}
return ring.get(hashVal).getPhysicalNode();
}
/**
* 找到某個物理節點的虛擬節點數量
*
* @param pNode 物理機節點
* @return 虛擬節點數量
*/
public int getExistingReplicas(T pNode) {
int replicas = 0;
for (VirtualNode<T> vNode : ring.values()) {
if (vNode.isVirtualNodeOf(pNode)) {
replicas++;
}
}
return replicas;
}
private static class MD5Hash implements HashFunction {
MessageDigest instance;
public MD5Hash() {
try {
instance = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
}
}
@Override
public long hash(String key) {
instance.reset();
instance.update(key.getBytes());
byte[] digest = instance.digest();
long h = 0;
for (int i = 0; i < 4; i++) {
h <<= 8;
h |= ((int) digest[i]) & 0xFF;
}
return h;
}
}
}
public interface HashFunction {
long hash(String key);
}
public interface Node {
String getKey();
}
public class VirtualNode<T extends Node> implements Node {
final T physicalNode;
final int replicaIndex;
public VirtualNode(T physicalNode, int replicaIndex) {
this.replicaIndex = replicaIndex;
this.physicalNode = physicalNode;
}
@Override
public String getKey() {
return physicalNode.getKey() + "-" + replicaIndex;
}
public boolean isVirtualNodeOf(T pNode) {
return physicalNode.getKey().equals(pNode.getKey());
}
public T getPhysicalNode() {
return physicalNode;
}
}
開源實現分析
Apache Cassandra:在數據分區(Data partitioning)中使用到一致性哈希
GlusterFS:文件分佈使用了一致性哈希,https://www.gluster.org/glusterfs-algorithms-distribution/
Akka:Akka 的 Router 使用了一致性哈希,https://doc.akka.io/docs/akka/snapshot/routing.html
參考
http://en.wikipedia.org/wiki/Consistent_hashing
http://arxiv.org/pdf/1406.2294v1.pdf
《大型網站高性能架構》