先说结论:不是普通Hash不行,是分布式场景下它太脆弱!
你以为取模就够了?Redis集群扩容时,数据重分布的噩梦才刚刚开始!
从一个真实事故说起
某电商系统,双11期间需要扩容缓存节点。使用传统Hash算法(hash % N)时:
// 传统Hash算法
public int getNode(String key) {
int hash = key.hashCode();
return Math.abs(hash % nodeCount);
}
结果:
- 节点数从10扩到20
- 80%的缓存失效
- 数据库直接被打垮
- 网站故障2小时
一位阿里工程师说过:
"在分布式系统中,增减节点是家常便饭,传统Hash算法就是一个定时炸弹"
一致性Hash是如何解决的?
核心思路:构建一个0~2^32-1的闭环,将节点和数据都映射到这个环上。
public class ConsistentHash<T> {
private final HashFunction hashFunction;
private final int numberOfReplicas; // 虚拟节点数
private final SortedMap<Integer, T> circle = new TreeMap<>();
public void addNode(T node) {
for (int i = 0; i < numberOfReplicas; i++) {
// 为每个节点创建虚拟节点
circle.put(hashFunction.hash(node.toString() + i), node);
}
}
public T getNode(String key) {
if (circle.isEmpty()) {
return null;
}
int hash = hashFunction.hash(key);
// 顺时针找到第一个大于等于hash的节点
if (!circle.containsKey(hash)) {
SortedMap<Integer, T> tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
}
实际效果对比
假设有4个节点:A、B、C、D,需要存储1000个数据。
传统Hash:
- 增加一个节点:约75%数据需要重分布
- 删除一个节点:约75%数据需要重分布
一致性Hash:
- 增加一个节点:约25%数据需要重分布
- 删除一个节点:约25%数据需要重分布
虚拟节点的妙用
数据倾斜是一致性Hash的一个问题,看这个优化版本:
// 引入虚拟节点
private void addVirtualNodes(String realNode) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
String virtualNode = realNode + "&&VN" + i;
int hash = getHash(virtualNode);
circle.put(hash, realNode);
}
}
效果:
- 数据分布更均匀
- 负载更加平衡
- 扩缩容更稳定
实战应用场景
Redis集群就是一个典型案例:
public String getRedisNode(String key) {
// 计算key的slot
int slot = CRC16.getSlot(key);
// 找到负责该slot的节点
return cluster.getNodeBySlot(slot);
}
其他应用:
- 分布式缓存
- 负载均衡
- 分库分表
- 服务发现
记住一句话: 一致性Hash不是银弹,但它确实是分布式系统的一个重要武器!
#分布式系统 #算法 #架构设计 #实战经验