java简单实现一致性哈希算法
来源:互联网 发布:不实名的.com域名 编辑:程序博客网 时间:2024/05/17 01:22
什么是一致性哈希算法
一种特殊的哈希算法,这种算法使得哈希表、集群的规模在伸缩时尽可能减少重映射(remap)。
为什么需要它
一致性哈希基本解决了在P2P环境中最为关键的问题——如何在动态的网络拓扑(集群)中分布存储和路由。每个节点仅需维护少量相邻节点的信息,并且在节点加入/退出系统时,仅有相关的少量节点参与到拓扑的维护中。
两种常见的一致性哈希算法
余数hash
hash_ip(请求者的ip的hashCode) % slot_Num(节点数) = n,n为节点的序号假设现在有3台缓存服务器,现在有20个ip来访问缓存集群,假设3个节点的序号为0~2,20个ip的hashCode为0~19,那么路由情况如下:如果扩展到4台服务器,那么只有标红色能命中缓存,并且随着服务器的增加,缓存的命中率递减
hash_ip012345678910111213141516171819路由到的节点01201201201201201201红色的为命中的缓存,黑色的缓存都shixiaohash_ip012345678910111213141516171819路由到的节点01230123012301230123从上面的例子中可以看出余数hash的伸缩性不好,当我们进行扩容时,多数缓存失效,压力全部移到数据库上,有可能导致数据库宕机。
这个问题的解决方案:
1.在网站访问量低的时候,比如凌晨,进行扩容
2.发送模拟请求进行缓存预热,使数据在缓存集群上重新分布
一致性hash算法
原理:先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 2的32次方-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 2的32次方-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
下图的大环表示hash环,蓝色点表示hashCode[node_ip]在环上的分布,小点表示hashCode[client_ip],顺时针寻找第一个大于hashCode[client_ip]的节点哈希值,即路由到该节点,由该节点处理请求。这种情况基本解决了伸缩性差的问题,我们随时可以添加删除节点到哈希环上。
但是节点的扩容又导致了一个问题:负载不均。如下图
当我们增加了node4节点时,那些本来会转发给node3的请求会被路由到node4上,导致node3处于空闲状态,而node4压力较大,这时,虚拟节点出现了,此时哈希环上的节点都是虚拟节点,多个虚拟节点映射到一个物理节点,也就是说,当我们添加一个节点时,我们不再把物理节点作为一个节点存到哈希环上,而是用多个虚拟节点来替代一个物理节点,路由到某个虚拟节点后再映射到物理节点。而这多个虚拟节点是分散的,很大程度可以弥补负载不均的缺点。
下图的黄色节点为新添加的虚拟节点,这3个节点实际上值对应一个物理节点,比如物理节点:10.0.0.101:1111,自定义虚拟节点(我在物理节点后加virtual node+序号):10.0.0.101:1111vn1、10.0.0.101:1111vn2、10.0.0.101:1111vn3,这样映射到哈希环上分散的,不会像一个节点那样导致扩容后负载不均,只要虚实节点的比例合理。
从网上找的,物理节点和虚拟节点的比例图,纵坐标表示物理节点数,横坐标表示虚拟节点数,比如物理节点只有10个时,需要100~200个虚拟节点
附带虚拟节点的一致性hash的java实现
1.使用FNV_1哈希算法来散列ip(java的hashCode值太集中,不适合)
2.用treeMap来存储节点的hash值,使用tail方法获取比hashCode[client_ip]大的子集,取子集的第一个节点
就是我们要的节点。
一种特殊的哈希算法,这种算法使得哈希表、集群的规模在伸缩时尽可能减少重映射(remap)。
为什么需要它
一致性哈希基本解决了在P2P环境中最为关键的问题——如何在动态的网络拓扑(集群)中分布存储和路由。每个节点仅需维护少量相邻节点的信息,并且在节点加入/退出系统时,仅有相关的少量节点参与到拓扑的维护中。
两种常见的一致性哈希算法
余数hash
hash_ip(请求者的ip的hashCode) % slot_Num(节点数) = n,n为节点的序号假设现在有3台缓存服务器,现在有20个ip来访问缓存集群,假设3个节点的序号为0~2,20个ip的hashCode为0~19,那么路由情况如下:如果扩展到4台服务器,那么只有标红色能命中缓存,并且随着服务器的增加,缓存的命中率递减
hash_ip012345678910111213141516171819路由到的节点01201201201201201201红色的为命中的缓存,黑色的缓存都shixiaohash_ip012345678910111213141516171819路由到的节点01230123012301230123从上面的例子中可以看出余数hash的伸缩性不好,当我们进行扩容时,多数缓存失效,压力全部移到数据库上,有可能导致数据库宕机。
这个问题的解决方案:
1.在网站访问量低的时候,比如凌晨,进行扩容
2.发送模拟请求进行缓存预热,使数据在缓存集群上重新分布
一致性hash算法
原理:先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 2的32次方-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 2的32次方-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。
下图的大环表示hash环,蓝色点表示hashCode[node_ip]在环上的分布,小点表示hashCode[client_ip],顺时针寻找第一个大于hashCode[client_ip]的节点哈希值,即路由到该节点,由该节点处理请求。这种情况基本解决了伸缩性差的问题,我们随时可以添加删除节点到哈希环上。
但是节点的扩容又导致了一个问题:负载不均。如下图
当我们增加了node4节点时,那些本来会转发给node3的请求会被路由到node4上,导致node3处于空闲状态,而node4压力较大,这时,虚拟节点出现了,此时哈希环上的节点都是虚拟节点,多个虚拟节点映射到一个物理节点,也就是说,当我们添加一个节点时,我们不再把物理节点作为一个节点存到哈希环上,而是用多个虚拟节点来替代一个物理节点,路由到某个虚拟节点后再映射到物理节点。而这多个虚拟节点是分散的,很大程度可以弥补负载不均的缺点。
下图的黄色节点为新添加的虚拟节点,这3个节点实际上值对应一个物理节点,比如物理节点:10.0.0.101:1111,自定义虚拟节点(我在物理节点后加virtual node+序号):10.0.0.101:1111vn1、10.0.0.101:1111vn2、10.0.0.101:1111vn3,这样映射到哈希环上分散的,不会像一个节点那样导致扩容后负载不均,只要虚实节点的比例合理。
从网上找的,物理节点和虚拟节点的比例图,纵坐标表示物理节点数,横坐标表示虚拟节点数,比如物理节点只有10个时,需要100~200个虚拟节点
附带虚拟节点的一致性hash的java实现
1.使用FNV_1哈希算法来散列ip(java的hashCode值太集中,不适合)
2.用treeMap来存储节点的hash值,使用tail方法获取比hashCode[client_ip]大的子集,取子集的第一个节点
就是我们要的节点。
3.简单起见,一个物理节点对应4个虚拟节点
package consistence_hash_algorithm;import java.util.SortedMap;import java.util.TreeMap;/** * writer: holien * Time: 2017-10-20 21:07 * Intent: 使用虚拟节点的一致性哈希算法 */public class ConsistentHashingWithVirtualNode { // 4个物理节点 private static String[] servers = {"168.10.10.101:6386", "168.10.10.101:6387", "168.10.10.101:6388", "168.10.10.101:6389"}; // 使用SortedMap可以排序,再使用tailMap获取key比hashCode[client_ip]大的子map private static SortedMap virtualNodes; private static final int VIRTUAL_NODE_NUM = 4; // 模拟线上的4台服务器对应的16个虚拟节点 static { virtualNodes = new TreeMap<Integer, String>(); for (int i = 0, len = servers.length ; i < len; i++) { int hashCode; String vitualNode; for (int j = 0; j < VIRTUAL_NODE_NUM; j++) { // 计算节点的哈希值作为key,节点ip("168.10.10.101:6386vni")作为value vitualNode = servers[i] + "vn" + j; // 计算虚拟节点的hashCode hashCode = getHashCode(vitualNode); virtualNodes.putIfAbsent(hashCode, vitualNode); System.out.println("添加了虚拟节点:" + vitualNode + ", hashCode:" + hashCode); } } } // 使用32位FNV_1计算节点的hashCode private static int getHashCode(String node) { final int p = 16777619; int hash = (int)2166136261L; for (int i = 0; i < node.length(); i++) hash = (hash ^ node.charAt(i)) * p; hash += hash << 13; hash ^= hash >> 7; hash += hash << 3; hash ^= hash >> 17; hash += hash << 5; // 如果算出来的值为负数则取其绝对值 if (hash < 0) hash = Math.abs(hash); return hash; } // 通过client_ip的哈希值路由一个虚拟节点,再映射到物理节点 public static String routeServer(String node) { int hashCode = getHashCode(node); SortedMap subMap = virtualNodes.tailMap(hashCode); int firstKey = (Integer)subMap.firstKey(); String virtualNode = (String)subMap.get(firstKey); // 模拟寻找物理节点,把vni去掉 String actualNode = virtualNode.substring(0, virtualNode.length() - 3); System.out.println(node + "的hashCode为" + hashCode + ",被路由到虚拟节点:" + virtualNode + ",映射到物理节点:" + actualNode); return actualNode; } public static void main(String[] args) { String[] nodes = {"10.112.11.252:1111", "221.226.0.1:2222", "10.211.0.1:3333"}; for (int i = 0, len = nodes.length; i < len; i++) { routeServer(nodes[i]); } }}
运行结果:
阅读全文
0 0
- java简单实现一致性哈希算法
- 一个简单的一致性哈希算法实现(Java版本)
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与java实现
- 一致性哈希算法与Java实现
- 一致性哈希算法与Java实现
- U-boot介绍
- 【Android 7.0 Audio 】Andriod Audio Overview
- 数组和指针的区别和联系?
- FP-growth算法——原理
- daughter of Pope Alexander seemed never
- java简单实现一致性哈希算法
- PHP交换两个变量的值,不使用第三个变量
- 进程控制
- Effective-C++学习笔记
- xml知识点
- she had murdered her lover; and now, when she had
- python3.6.3+opencv3.3.0学习笔记八--本地视频人脸识别后另存
- 集合 泛型 迭代器
- 中学语文说课稿《梦游天姥吟留别》