起源
在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
《大型网站高性能架构》