一致性哈希是一种特殊的哈希算法,主要用于分布式系统,解决数据分布和节点动态变化的问题。相比传统哈希算法,它能够有效减少因服务器扩容或缩容导致的缓存重建问题,广泛应用于分布式缓存、分布式存储、负载均衡等场景。
1. 传统哈希 vs 一致性哈希
(1)传统取模哈希
在常规哈希方案中,我们通常使用取模算法进行分片:
[
\text{server} = \text{hash}(\text{key}) \mod N
]
其中 ( N ) 是服务器的总数。例如:
-
假设有 3 台服务器(编号 0、1、2),对数据
user123
进行哈希:server = hash("user123") % 3 = 1
数据
user123
存在 Server 1。 -
问题:扩容或缩容时,大量数据需要迁移
- 例如:如果扩容到 4 台服务器,原本的
mod 3
变成mod 4
,那么所有数据的存储位置都会变化,大量数据需要重新分配,造成 缓存雪崩。
- 例如:如果扩容到 4 台服务器,原本的
(2)一致性哈希
一致性哈希使用哈希环的概念,减少数据迁移:
- 将服务器映射到一个固定的哈希环(0 ~ 2³² - 1)。
- 对每个数据计算哈希值,并放入顺时针方向的最近服务器。
- 扩容/缩容时,只影响哈希环上的局部数据,减少数据迁移量。
2. 一致性哈希的实现
(1)哈希环
- 使用一个 0 ~ 2³²-1 的哈希空间,并将服务器的哈希值映射到该空间上。
- 数据的哈希值也映射到该空间,并顺时针找到最近的服务器存储数据。
示例:
假设有 3 台服务器:
Server A -> hash(A) = 10
Server B -> hash(B) = 30
Server C -> hash(C) = 60
当数据 key123
进行哈希:
hash(key123) = 25 → 由于 25 在 (10, 30] 区间,存入 Server B
(2)服务器增减对数据影响
- 扩容:
新增 Server D (hash(D) = 20) 后,落在 (10, 20] 之间的数据会从 Server B 迁移到 Server D,而 hash(key123) = 25 不受影响,仍然在 Server B 上,只影响 Server D 和 Server B 之间的数据。
- 缩容:如果
Server B
挂掉,则hash(key123) = 25
顺时针找到Server C
,同样只影响部分数据。
3. 虚拟节点(解决数据倾斜问题)
问题:服务器分布不均匀,导致数据倾斜
- 如果 3 台服务器
A (10)
,B (30)
,C (60)
,但A-C
之间间距大,那么 C 可能存储的数据远多于 A、B。
解决方案:使用虚拟节点
- 在环上为每台服务器创建多个虚拟副本(通过不同哈希计算),均匀分布:
这样可以使服务器负载均衡,避免某个服务器存储过多数据。Server A -> hash(A#1) = 10, hash(A#2) = 15, hash(A#3) = 20 Server B -> hash(B#1) = 30, hash(B#2) = 35, hash(B#3) = 40
4. 一致性哈希的应用
- 分布式缓存(Redis, Memcached)
- 一致性哈希用于 Redis 缓存集群,减少缓存失效的影响。
- 分布式存储(HDFS, Ceph)
- 存储节点的动态调整不会影响整个集群。
- 负载均衡(Nginx, LVS)
- 反向代理服务器使用一致性哈希进行请求分配。
- 分布式数据库(ShardingSphere, MySQL 分片)
- 数据库分片采用一致性哈希,避免大规模数据迁移。
5. Java 实现一致性哈希
(1)基本实现
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.SortedMap;
import java.util.TreeMap;public class ConsistentHashing {private final TreeMap<Integer, String> ring = new TreeMap<>();private final int virtualNodes = 3; // 虚拟节点个数public void addServer(String server) {for (int i = 0; i < virtualNodes; i++) {int hash = hash(server + "#" + i);ring.put(hash, server);}}public void removeServer(String server) {for (int i = 0; i < virtualNodes; i++) {int hash = hash(server + "#" + i);ring.remove(hash);}}public String getServer(String key) {if (ring.isEmpty()) return null;int hash = hash(key);SortedMap<Integer, String> tailMap = ring.tailMap(hash);Integer targetHash = tailMap.isEmpty() ? ring.firstKey() : tailMap.firstKey();return ring.get(targetHash);}private int hash(String key) {try {MessageDigest md = MessageDigest.getInstance("MD5");byte[] bytes = md.digest(key.getBytes(StandardCharsets.UTF_8));return ((bytes[0] & 0xFF) << 24) | ((bytes[1] & 0xFF) << 16) | ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF);} catch (Exception e) {throw new RuntimeException(e);}}public static void main(String[] args) {ConsistentHashing ch = new ConsistentHashing();ch.addServer("ServerA");ch.addServer("ServerB");ch.addServer("ServerC");System.out.println("key123 存入: " + ch.getServer("key123"));System.out.println("key456 存入: " + ch.getServer("key456"));ch.addServer("ServerD");System.out.println("扩容后 key123 存入: " + ch.getServer("key123"));}
}
6. 总结
✅ 适用于分布式系统,解决服务器动态变更导致的数据迁移问题。
✅ 比取模哈希更稳定,减少缓存击穿问题。
✅ 结合虚拟节点可以均衡负载,避免数据倾斜。
❌ 仍然存在一定的计算开销,尤其是哈希计算和虚拟节点管理。
一致性哈希广泛应用于分布式存储、负载均衡、分布式数据库等场景,是现代分布式架构中不可或缺的技术。