文档章节

一致性hash算法及java实现

S
 SimonTao
发布于 01/20 12:09
字数 1660
阅读 51
收藏 0

典型的应用场景是: 有N台服务器提供缓存服务,需要对服务器进行负载均衡,将请求平均分发到每台服务器上,每台机器负责1/N的服务。

常用的算法是对hash结果取余数 (hash() mod N ):对机器编号从0到N-1,按照自定义的 hash()算法,对每个请求的hash()值按N取模,得到余数i,然后将请求分发到编号为i的机器。但这样的算法方法存在致命问题,如果某一台机器宕机,那么应该落在该机器的请求就无法得到正确的处理,这时需要将当掉的服务器从算法从去除,此时候会有(N-1)/N的服务器的缓存数据需要重新进行计算;如果新增一台机器,会有N /(N+1)的服务器的缓存数据需要进行重新计算。

一致性哈希算法(Consistent Hashing Algorithm)是一种分布式算法,常用于负载均衡。Memcached client也选择这种算法,解决将key-value均匀分配到众多Memcached server上的问题。它可以取代传统的取模操作,解决了取模操作无法应对增删Memcached Server的问题(增删server会导致同一个key,在get操作时分配不到数据真正存储的server,命中率会急剧下降)。

简单来说,一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0 - (2^32)-1(即哈希值是一个32位无符号整形)

以下是自己总结的:

不带虚拟节点的一致性hash算法流程

1、定义一个服务器列表信息;

2、将服务器列表计算出hash值,并添加到map中(或者redis中);

3、计算出key值得hash值;

4、在map中取出大于此hash值得列表;

5、如果有则顺时针取出离node最近节点的服务器;

6、如果没有则取出map中第一个节点即可;

7、完毕;

代码如下:

//待添加入Hash环的服务器列表
private static String[] servers = { "192.168.0.1:8080", "192.168.0.2:8080",
 "192.168.0.3:8080", "192.168.0.4:8080", "192.168.0.5:8080" };
 
//key表示服务器的hash值,value表示服务器
private static SortedMap<Integer, String> sortedMap = new TreeMap<Integer, String>();
 
//程序初始化,将所有的服务器放入sortedMap中
static {
 for (int i=0; i<servers.length; i++) {
 int hash = getHash(servers[i]);
 System.out.println("[" + servers[i] + "]加入集合中, 其Hash值为" + hash);
 sortedMap.put(hash, servers[i]);
 }
 System.out.println();
}
 
//得到应当路由到的结点
private static String getServer(String key) {
 //得到该key的hash值
 int hash = getHash(key);
 //得到大于该Hash值的所有Map
 SortedMap<Integer, String> subMap = sortedMap.tailMap(hash);
 if(subMap.isEmpty()){
 //如果没有比该key的hash值大的,则从第一个node开始
 Integer i = sortedMap.firstKey();
 //返回对应的服务器
 return sortedMap.get(i);
 }else{
 //第一个Key就是顺时针过去离node最近的那个结点
 Integer i = subMap.firstKey();
 //返回对应的服务器
 return subMap.get(i);
 }
}
 
//使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
private static 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;
 
 // 如果算出来的值为负数则取其绝对值
 if (hash < 0)
 hash = Math.abs(hash);
 return hash;
}
 
public static void main(String[] args) {
 String[] keys = {"香蕉", "菠萝", "蜂蜜"};
 for(int i=0; i<keys.length; i++)
 System.out.println("[" + keys[i] + "]的hash值为" + getHash(keys[i])
 + ", 被路由到结点[" + getServer(keys[i]) + "]");
}

带虚拟接待的一致性hash算法流程

1、先把原始的服务器添加到真实结点列表中;

2、再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高;

3、得到该key的hash值;

4、得到大于该Hash值的所有Map;

5、如果没有比该key的hash值大的,则从第一个node开始;

6、如果有则第一个Key就是顺时针过去离node最近的那个结点

7、返回对应的服务器;

8、virtualNode虚拟节点名称要截取一下;

9、结束

代码如下:

//待添加入Hash环的服务器列表
private static String[] servers = {"192.168.0.1:8080", "192.168.0.2:8080", "192.168.0.3:8080",
 "192.168.0.4:8080", "192.168.0.5:8080"};
 
//真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
private static List<String> realNodes = new LinkedList<String>();
 
//虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
private static SortedMap<Integer, String> virtualNodes = new TreeMap<Integer, String>();
 
//虚拟节点的数目,这里写死,为了演示需要,一个真实结点对应5个虚拟节点
private static final int VIRTUAL_NODES = 5;
 
static{
 //先把原始的服务器添加到真实结点列表中
 for(int i=0; i<servers.length; i++)
 realNodes.add(servers[i]);
 
 //再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
 for (String str : realNodes){
 for(int i=0; i<VIRTUAL_NODES; i++){
 String virtualNodeName = str + "&&VN" + String.valueOf(i);
 int hash = getHash(virtualNodeName);
 System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
 virtualNodes.put(hash, virtualNodeName);
 }
 }
 System.out.println();
}
 
//使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
private static 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;
 
 // 如果算出来的值为负数则取其绝对值
 if (hash < 0)
 hash = Math.abs(hash);
 return hash;
}
 
//得到应当路由到的结点
private static String getServer(String key){
 //得到该key的hash值
 int hash = getHash(key);
 // 得到大于该Hash值的所有Map
 SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
 String virtualNode;
 if(subMap.isEmpty()){
 //如果没有比该key的hash值大的,则从第一个node开始
 Integer i = virtualNodes.firstKey();
 //返回对应的服务器
 virtualNode = virtualNodes.get(i);
 }else{
 //第一个Key就是顺时针过去离node最近的那个结点
 Integer i = subMap.firstKey();
 //返回对应的服务器
 virtualNode = subMap.get(i);
 }
 //virtualNode虚拟节点名称要截取一下
 if(!StringUtils.isEmpty(virtualNode)){
 return virtualNode.substring(0, virtualNode.indexOf("&&"));
 }
 return null;
}
 
public static void main(String[] args){
 String[] keys = {"香蕉", "菠萝", "蜂蜜"};
 for(int i=0; i<keys.length; i++)
 System.out.println("[" + keys[i] + "]的hash值为" +
 getHash(keys[i]) + ", 被路由到结点[" + getServer(keys[i]) + "]");
}

如有疑问,请留言,我会一一解答。

一致性hash算法及java实现

© 著作权归作者所有

S
粉丝 1
博文 22
码字总数 10298
作品 0
朝阳
私信 提问
加载中

评论(0)

25岁小伙分享高质量Java面试题,这些你能答得上来吗(读者福利)

前言 前段时间经常看到推送某某某面试哪家成功,某某某面试哪家失败,这都是稀松平常的事情,那么为什么有的人面试经常受挫,有的人面试一帆风顺。其中缘由众说纷纭,相信大家也经历和很多面...

我最喜欢三大框架
2019/06/14
35
0
Memcached - In Action

Memcached 标签 : Java与NoSQL With Java 比较知名的Java Memcached客户端有三款:Java-Memcached-Client、XMemcached以及Spymemcached, 其中以XMemcached性能最好, 且维护较稳定/版本较新:...

菜鸟-翡青
2016/06/14
0
0
2020年阿里Java面试必问:JVM与性能优化+Redis+设计模式+分布式

前言 一年之计在于春 金三银四已经要到来,2020的新的开始,作为一个开发人员,你是否面上了自己理想的公司,薪资达到心中理想的高度? 面试:如果不准备充分的面试,完全是浪费时间,更是对...

java周某人
04/01
0
0
Hive集群合并之应用端的负载均衡算法

0.背景 有这么一个场景,我们有两个Hive集群,Hive集群1(后面成为1号集群)是一直专享于数据计算平台的,而Hive集群2(后面成为2号集群)是用于其他团队使用的,比如特征,广告等。而由此存...

2019/01/29
0
0
一致性Hash算法(KetamaHash)的c#实现

最近在研究"一致性HASH算法"(Consistent Hashing),用于解决memcached集群中当服务器出现增减变动时对散列值的影响。后来 在JAVAEYE上的一篇文章中,找到了其中的 KetamaHash 算法的JAVA实...

长平狐
2012/11/06
150
0

没有更多内容

加载失败,请刷新页面

加载更多

展示如何在checkout里使用quote,quote item, address, shopping cart

展示如何更改并且在定制化的时候高效应用这些模块。 以下实体继承 \Magento\Framework\Model\AbstractExtensibleModel ,所以你可以使用第4章中讨论的可扩展属性。 Quote Quotes 是客户购物车...

忙碌的小蜜蜂
19分钟前
8
0
面向对象思想设计原则及常见设计模式

1、面向对象思想设计原则 在实际的开发中,我们要想更深入的了解面向对象思想,就必须熟悉前人总结过的面向对象的思想的设计原则 1.1、单一职责原则 高内聚,低耦合 每个类应该只有一个职责,...

庭前云落
28分钟前
25
0
fastadmin对接支付宝支付,遇到的问题之一二

一开始也没做过支付宝支付相关的东西 本来用的fastadmin的epay插件来配置支付宝的,本来以为会so easy,但是实际上还是遇到了一些问题,花了几天时间,把沙箱环境配置起来了... 算是一个良好的开...

老bia同学
28分钟前
5
0
记录一题生产者消费者问题

//有一个容器,能存储一定的产品,有put和get方法,有两个生产者,8个消费者的线程阻塞 import java.util.LinkedList; import java.util.concurrent.TimeUnit; public class Test3<T> { Lin...

南桥北木
39分钟前
13
0
线程池源码解读——回归基础

线程池源码解读——回归基础 线程池源码解读——回归基础 线程池的好处: JDK提供的创建线程池: java 中创建线程的方式: 线程池源码解读: 记录的知识点: 线程池的好处: 降低资源的开销 ...

lihua20103181
41分钟前
86
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部