圆环的正上方的点代表0 , 0点右侧的第一个点代表1 , 以此类推 , 2、3、4、5、6……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1。我们把这个由2的32次方个点组成的圆环称为hash环 。
假设我们有3台缓存服务器(A/B/C) , 使用它们各自的IP地址进行哈希计算 , 使用哈希后的结果对2^32取模 , 可以使用如下公式:
hash("服务器的IP地址") % 2^32
通过上述公式算出的结果一定是一个0到2^32-1之间的一个整数 , 我们就用算出的这个整数 , 分别代表服务器(A/B/C).
既然这个整数肯定处于0到2^32-1之间 , 那么 , 上图中的hash环上必定有一个点与这个整数对应 , 也就是服务器A/B/C就可以映射到这个环上 , 如下图:
文章插图
假设 , 我们需要使用Redis缓存数据 , 那么我们使用如下公式可以将数据映射到上图中的hash环上
hash(key) % 2^32现在服务器与数据都被映射到了hash环上 , 上图中的数据将会被缓存到服务器A上 。
因为从数据的开始位置 , 沿顺时针方向遇到的第一个服务器就是A服务器 , 所以 , 上图中的数据将会被缓存到服务器A上 。
文章插图
1.5.1.2、一致性hash算法有缺点
优点
添加或移除节点时 , 数据只需要做部分的迁移 , 比如上图中把C服务器移除 , 则数据4迁移到服务器A中 , 而其他的数据保持不变 。
缺点
数据分布不均匀 , 可能出现所有的key都被hash到同一个节点上了 , 折中现象叫做hash环偏移 。
理论上我们可以增加服务器数量来减少便宜 , 但是这样成本太高了 。所以通过增加虚拟节点来处理 。
1.5.1.3、一致性hash虚拟节点
"虚拟节点"是"实际节点"(实际的物理服务器)在hash环上的复制品,一个实际节点可以对应多个虚拟节点 。
从上图可以看出 , A、B、C三台服务器分别虚拟出了一个虚拟节点 , 当然 , 如果你需要 , 也可以虚拟出更多的虚拟节点 。
引入虚拟节点的概念后 , 缓存的分布就均衡多了 , 上图中 , 1和3号数据被缓存在服务器A中 , 4和5号数据被缓存在服务器B中 , 2和6号数据被缓存在服务器C中.
如果你还不放心 , 可以虚拟出更多的虚拟节点 , 以便减小hash环偏斜所带来的影响 , 虚拟节点越多 , hash环上的节点就越多 , 缓存被均匀分布的概率就越大 。
文章插图
一般来说 , 一致性hash算法+虚拟节点就是一个很好的方案了 。
1.5.1.4、一致性hash算法实现
一致性hash算法(无虚拟节点)
public class HashDemo { public static void main(String[] args) { //step1 初始化:把服务器节点IP的哈希值对应到哈希环上 // 定义服务器ip String[] servers = new String[]{"192.168.222.101", "192.168.222.102", "192.168.222.103"}; // 创建一个排序的hashMap,key存储hash值 , value存储服务器IP地址 , 并按照Hash值排序 SortedMap hashServerMap = new TreeMap<>(); for (String redisServer : servers) { // 求出每?个ip的hash值 , 对应到hash环上 , 存储hash值与ip的对应关系 int serverHash = Math.abs(redisServer.hashCode()); // 存储hash值与ip的对应关系 hashServerMap.put(serverHash, redisServer); } //step2 针对客户端IP求出hash值 // 定义客户端传递过来的RedisKey String[] redisKeys = new String[]{"user:001:name", "user:001:age", "user:001:sex"}; for (String redisKey : redisKeys) { // 计算redisKey的hash值 int redisKeyHash = Math.abs(redisKey.hashCode()); //step3 针对客户端,找到能够处理当前RedisKey的服务器(哈希环上顺时针最近) // 根据redisKey的哈希值去找出哪?个服务器节点能够处理 // tailMap返回此映射的键大于或等于fromKey的部分 , 也就是比redisKey的hash值大的排序列表 , 取第一个就是最近的服务器节点 SortedMap filteredSortedMap = hashServerMap.tailMap(redisKeyHash); // 获取key落到那台服务器 , filteredSortrdMap为空 , 直接取服务器列表hashServerMap第一个 , 不为空 , 则取出最近一个filteredSortrdMap Integer hashKey = filteredSortedMap.isEmpty() ? hashServerMap.firstKey() : filteredSortedMap.firstKey(); System.out.println("==========>>>>RedisKey:" + redisKey + " 被路由到服务器:" + hashServerMap.get(hashKey)); } } }
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Redis 的大 Key 对持久化有什么影响?
- 手机防窥膜的原理是什么?真的会影响视力吗?
- 碘酒消毒的原理是什么?
- 三大指纹识别原理 指纹识别技术
- 玄空风水原理和方法 大玄空风水
- 图解涡轮增压器工作原理 涡轮增压器工作原理
- 跳绳减肥原理是什么
- 瘦身舞蹈瘦腿的原理是什么?
- 详解反渗透膜(RO 反渗透膜原理工作原理)
- 精索静脉曲张手术原理
