一致性哈希算法
一、什么是一致性哈希
一致性哈希算法(Consistent Hashing)是一种特殊的哈希算法,在移除或添加服务器时,能够尽可能小地改变已存在的键值映射关系。主要应用于分布式缓存系统中,解决节点动态增减时的数据重新分配问题。
传统哈希的问题
假设有 3 台缓存服务器,使用传统取模方式分配数据:
int serverIndex = hash(key) % serverCount;
当服务器数量变化时:
- 增加节点:serverCount 从 3 变为 4,大部分数据的映射关系都会改变
- 删除节点:serverCount 从 3 变为 2,同样导致大量数据重新分配
这会导致缓存大面积失效,引发缓存雪崩。
二、一致性哈希原理
2.1 哈希环
一致性哈希将整个哈希值空间组织成一个虚拟的圆环(Hash Ring),范围为 0 ~ 2^32-1:
- 服务器节点映射:对每台服务器的 IP 或主机名进行哈希,映射到环上的某个位置
- 数据映射:对数据的 key 进行哈希,映射到环上的某个位置
- 查找规则:从数据位置开始顺时针查找,遇到的第一个服务器节点即为存储位置
2.2 节点增减的影响
删除节点:
- 只影响该节点到其前一个节点之间的数据
- 这些数据会迁移到下一个节点
添加节点:
- 只影响新节点到其前一个节点之间的数据
- 部分数据从下一个节点迁移到新节点
相比传统方式,数据迁移量大幅减少。
三、虚拟节点
3.1 数据倾斜问题
当服务器节点较少时,可能出现数据分布不均的情况。例如 3 台服务器在环上的位置可能很接近,导致某台服务器承载了大部分数据。
3.2 虚拟节点解决方案
为每台物理服务器创建多个虚拟节点(Virtual Node),均匀分布在哈希环上:
// 为每台服务器创建 150 个虚拟节点
for (int i = 0; i < 150; i++) {
String virtualNodeName = "Server1#" + i;
int hash = getHash(virtualNodeName);
virtualNodes.put(hash, "Server1");
}
优势:
- 虚拟节点越多,数据分布越均匀
- 一般 100-200 个虚拟节点即可达到良好效果
四、Java 实现
public class ConsistentHash<T> {
// 虚拟节点数量
private final int virtualNodeCount;
// 哈希环
private final SortedMap<Integer, T> ring = new TreeMap<>();
public ConsistentHash(int virtualNodeCount) {
this.virtualNodeCount = virtualNodeCount;
}
/**
* 添加服务器节点
*/
public void addNode(T node) {
for (int i = 0; i < virtualNodeCount; i++) {
String virtualNodeName = node.toString() + "#" + i;
int hash = getHash(virtualNodeName);
ring.put(hash, node);
}
}
/**
* 删除服务器节点
*/
public void removeNode(T node) {
for (int i = 0; i < virtualNodeCount; i++) {
String virtualNodeName = node.toString() + "#" + i;
int hash = getHash(virtualNodeName);
ring.remove(hash);
}
}
/**
* 获取数据应该存储的节点
*/
public T getNode(String key) {
if (ring.isEmpty()) {
return null;
}
int hash = getHash(key);
// 顺时针查找第一个节点
SortedMap<Integer, T> tailMap = ring.tailMap(hash);
// 如果没有找到,返回第一个节点(形成环)
int nodeHash = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();
return ring.get(nodeHash);
}
/**
* FNV1_32_HASH 算法
*/
private int getHash(String str) {
final int p = 16777619;
int hash = (int) 2166136261L;
for (int i = 0; i < str.length(); i++) {
hash = (hash ^ str.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
return Math.abs(hash);
}
}
使用示例
// 创建一致性哈希,每个节点 150 个虚拟节点
ConsistentHash<String> consistentHash = new ConsistentHash<>(150);
// 添加服务器节点
consistentHash.addNode("192.168.1.1:8080");
consistentHash.addNode("192.168.1.2:8080");
consistentHash.addNode("192.168.1.3:8080");
// 查找数据应该存储的节点
String server = consistentHash.getNode("user:1001");
System.out.println("数据应该存储在: " + server);
// 添加新节点
consistentHash.addNode("192.168.1.4:8080");
// 删除节点
consistentHash.removeNode("192.168.1.2:8080");
五、应用场景
5.1 分布式缓存
- Memcached 客户端:使用一致性哈希分配缓存数据
- Redis Cluster:使用哈希槽(Hash Slot)的变种实现
5.2 负载均衡
- Nginx:consistent hash 模块实现会话粘滞
- Dubbo:ConsistentHashLoadBalance 负载均衡策略
5.3 分布式存储
- Cassandra:使用一致性哈希分配数据到不同节点
- DynamoDB:基于一致性哈希的分区策略
六、优缺点分析
优点
- 单调性:节点增减时,已有映射关系不会改变,只会增加新映射
- 分散性:数据均匀分布在各个节点上
- 平衡性:通过虚拟节点避免数据倾斜
- 可扩展性:方便动态增减节点
缺点
- 数据倾斜:节点较少时可能分布不均(虚拟节点可解决)
- 实现复杂:相比简单取模方式,实现和维护成本较高
- 虚拟节点开销:需要维护大量虚拟节点
七、总结
一致性哈希算法通过引入哈希环和虚拟节点的概念,有效解决了分布式系统中节点动态增减导致的数据大规模迁移问题。在分布式缓存、负载均衡、分布式存储等场景中有广泛应用。
核心要点:
- 哈希环将数据和节点映射到同一空间
- 顺时针查找规则确定数据存储位置
- 虚拟节点保证数据分布均匀
- 节点变化时只影响相邻节点的数据
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 逐光の博客!
评论
