目录

一、案例背景

1.1 系统简介

1.2 遇到问题

1.3 初步优化

1.4 继续优化

二、使用一致性Hash解决问题

三、一致性Hash介绍

3.1 理论简介

3.2 设计实现

四、对一致性Hash的理解

4.1 应用场景

4.2 环的Hash数量的选择

4.3 虚拟节点的作用

4.4 Hash算法的选择

4.5 一致性Hash的替代

五、案例

5.1 需求

5.2 算法

5.3 测试

5.5 多次实验结果


一、案例背景

1.1 系统简介

  首先看一下系统架构,方便解释:

  页面给用户展示的功能就是,可以查看任何一台机器的某些属性(以下简称系统信息)。

  消息流程是,页面发起请求查看指定机器的系统信息到后台,后台可以查询到有哪些server在提供服务,根据负载均衡算法(简单的轮询)指定由哪个server进行查询,并将消息发送到Kafka,然后所有的server消费Kafka的信息,当发现消费的信息要求自己进行查询时,就连接指定的machine进行查询,并将结果返回回去。

  Server是集群架构,可能动态增加或减少。

  至于架构为什么这么设计,不是重点,只能说这是符合当时环境的最优架构。

1.2 遇到问题

  遇到的问题就是慢,特别慢,经过初步核实,最耗时的事是server连接machine的时候,基本都要5s左右,这是不能接受的。

1.3 初步优化

  因为耗时最大的是server连接machine的时候,所以决定在server端缓存machine的连接,经过测试如果通过使用的连接缓存进行查询,那么耗时将控制在1秒以内,满足了用户的要求,不过还有一个问题因此产生,那就是根据现有负载均衡算法,假如server1已经缓存了到machine1的连接,但是再次查询时,请求就会发送到下一个server,如server2,这就导致了两个问题,一是,重新建立了连接耗时较长,二是,两个server同时缓存着到machine1的连接,造成了连接浪费。

1.4 继续优化

  一开始想到最简单的就是将查询的machine进行hash计算,并除sever的数量取余,这样保证了查询同一个machine时会要求同一个server进行操作,满足了初步的需求。但是因为server端是集群,机器有可能动态的增加或减少,假如根据hash计算,指定的 machine会被指定的server连接,如下图:

  然后又增加了一个server,那么根据当前的hash算法,server和machine的连接就会变成如下:

  可以发现,四个machine和server的连接关系发生变化了,这将导致4次连接的初始化,以及四个连接的浪费,虽然server集群变动的几率很小,但是每变动一次将有一半的连接作废掉,这还是不能接受的,当时想的最理想的结果是:

  • 当新增机器的时候,原有的连接分一部分给新机器,但是除去分出的连接以外保持不变
  • 当减少机器的时候,将减少机器的连接分给剩下的机器,但剩下机器的原有连接不变

  简单来说,就是变动不可避免但是让变动最小化。根据这种思想,就想到了一致性hash,觉得这个应该可以满足要求。

二、使用一致性Hash解决问题

  一致性Hash的定义或者介绍在第三节,现在写出一致性Hash的Java的解决方法。只写出示例实现代码,首先最重要的就是Hash算法的选择,根据现有情况以及已有Hash算法的表现,选择了FNV Hash算法,以下是其实现:

public static int FnvHash(String key) {final int p = 16777619;long hash = (int) 2166136261L;for (int i = 0,n = key.length(); i < n; i++){hash = (hash ^ key.charAt(i)) * p;}hash += hash << 13;hash ^= hash >> 7;hash += hash << 3;hash ^= hash >> 17;hash += hash << 5;return ((int) hash & 0x7FFFFFFF);
}

  然后是对能提供服务的server进行预处理:

public static ConcurrentSkipListMap<Integer, String> init(){//创建排序Map方便后面的计算ConcurrentSkipListMap<Integer,String> servers=new ConcurrentSkipListMap<>();//获得可以提供服务的serverList<String> serverUrls=Arrays.asList("192.168.2.1:8080","192.168.2.2:8080","192.168.2.3:8080");//将server依次添加到Map中for(String serverUrl:serverUrls){servers.put(FnvHash(serverUrl), serverUrl);//以下三个是当前server的三个虚拟节点,Hash不同servers.put(FnvHash(serverUrl+"#1"), serverUrl);servers.put(FnvHash(serverUrl+"#2"), serverUrl);servers.put(FnvHash(serverUrl+"#3"), serverUrl);}return servers;
}

  这段代码将能提供的server放入排序Map,键为其Hash值,值为server的主机和IP,接下来就要对每一个请求的要连接的machin计算需要哪一个server进行连接:

/*** @param machine 要连接的机器* @param servers 可提供服务的server* @return*/
private static String getServer(int machine, ConcurrentSkipListMap<Integer, String> servers) {int left=Integer.MAX_VALUE;int right=Integer.MAX_VALUE;int leftDis=0;int rightDis=0;for(Entry<Integer, String> server:servers.entrySet()){int key=server.getKey();if(key<machine){left=key;}else{right=key;}if(right!=Integer.MAX_VALUE){break;}}if(left==Integer.MAX_VALUE){left=servers.lastKey();leftDis=Integer.MAX_VALUE-left+machine;}else{leftDis=machine-left;}if(right==Integer.MAX_VALUE){right=servers.firstKey();rightDis=Integer.MAX_VALUE-machine+right;}else{rightDis=right-machine;}return servers.get(rightDis<=leftDis?right:left);
}

  这个方法就是计算,具体逻辑可以在看完下一节有更深的了解。

  经过上面的三个方法就解决了上面提出的要求,经过测试也完美,或许算法还看不懂,也或许一致Hash算法还不知道是什么,虚拟节点是什么,但是现在应该了解需求是怎么产生的,已经通过什么满足了要求,现在唯一要做的就是了解一致性Hash了,下面进行介绍。

三、一致性Hash介绍

3.1 理论简介

  一致性Hash的简介,摘自百度百科。

  一致性哈希算法在1997年由麻省理工学院提出,设计目标是为了解决因特网中的热点(Hot spot)问题。一致性哈希提出了在动态变化的Cache环境中,哈希算法应该满足的4个适应条件:

均衡性(Balance):

  平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
单调性(Monotonicity):

  单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲区加入到系统中,那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去,而不会被映射到旧的缓冲集合中的其他缓冲区。(这段翻译信息有负面价值的,当缓冲区大小变化时一致性哈希(Consistent hashing)尽量保护已分配的内容不会被重新映射到新缓冲区。)

分散性(Spread):

  在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
负载(Load):

  负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

3.2 设计实现

  一般的一致性Hash的设计实现都是按照如下方式:

  首先所有的Hash值应该构成一个环,就像钟表的时刻一样,也就是说有明确的Hash最大值,环内Hash的数量一般为2的32次方:

  将server通过Hash计算映射到环上,注意选取能区别开server的唯一属性,比如ip加端口:

  然后所有的把所有的请求使用唯一的属性计算Hash值,然后请求到最近的server上面:

  假如有新机器加入时:

  新机器相邻的请求会被重新定向到新的server,如果有机器挂掉的话,挂掉机器的请求也会重新分配给就近的server:

  通过上面的图例讲解,应该可以看出环形设计的好处,那就是不管新增还是减少机器,变动的都是变动机器附近的请求,已有请求的映射不会变动到已有的节点上。

四、对一致性Hash的理解

4.1 应用场景

  通过一致性Hash的特性来看,一致性Hash极力保证变动的最小化,比较适用于有状态连接,如果连接是无状态的,那么完全没必要使用这种算法,轮询或者随机都是可以的。效率要比一致性Hash高,省去了每一次请求的计算过程。

4.2 环的Hash数量的选择

  本质上没有特殊的要求,选取的原则可以考虑以下几点:

  1. Hash数量最好最够多,因为要考虑未来新增server的情况,以及虚拟节点的添加
  2. Hash数量的最大值在int范围内即可,int最大值已经足够大,大于int的会相对增加计算和存储成本
  3. Hash数量的最大值的另一个参考要点,就是选取Hash算法的最大值

  所以上面的例子,环Hash数量选择了2^32,恰好fnv Hash算法的最大值也是它,FNV Hash算法参照此。

4.3 虚拟节点的作用

  看过上面代码的应该知道,对server进行Hash的时候,会同时创建server的几个虚拟节点,它们同样代表着它们的server,有如下作用:

  1. 防止server的Hash重复,虽然Hash重复的概率少之又少,但是依然不能完全避免,所以通过使用多个虚拟节点,可以避免因server的Hash重复导致server被完全覆盖掉
  2. 有利于负载均衡,如果每个server只有一个节点,那么有可能分布的不均匀,这时候通过多个虚拟节点,可以增加均匀分布的可能性,当然这依赖于Hash算法的选择

  至于虚拟节点的数量,这个没有硬性要求,节点的数量越多,负载均衡越好,但是计算量也越大,如果考虑到server集群的易变性,每一次请求都需要重新计算server及其虚拟节点的Hash值,那么节点的数量不要太大,不然也是一个性能的瓶颈。

4.4 Hash算法的选择

  Hash算法有很多种,上面fnv hash的可以参考一下,至于其他的,考虑以下几点就可以:

  • 不要自己写Hash算法,用已有的就可以,出于学习的目的可以写,生产环境用已有的Hash算法
  • 算法速度一定要快
  • 同一个输入的值,要有相同的输出
  • Hash值足够散列,Hash碰撞概率低

  考虑以上几点就可以了,后续会针对Hash算法,写一篇博客。

4.5 一致性Hash的替代

  不用一致Hash可不可以,能不能满足相同的需求,答案是可以的,那就是主动维护一个路由表。基本要做以下操作:

  1. 首先获得当前提供服务的server
  2. 当有请求来临时,先判断当前请求是否已有对应的server,若有交由对应的server,若无,选择负载最低的一个server,并存记录
  3. 当server挂掉以后,新的请求重新走2步骤
  4. 当有新的server加入时,可以主动负载均衡,也可以重新走2步骤

  优缺点简单说一下:

优点:

  • 负载更加均衡,甚至可以保证完全的均衡,因为不依赖Hash的不确定性
  • 整个分配过程人为掌握,当某些请求必须分配到指定的server上,修改更简单

缺点:

  • 编码量大,需要严格测试
  • 需要主动维护一个路由表,存储是一个需要考虑的问题
  • 请求量大时,路由表容量会增大,可以考虑存入Redis中

五、案例

5.1 需求

1万个设备的IMEI固定且尽量平均的分配到四个组。

5.2 算法

public static int FnvHash(String key) {final int p = 16777619;long hash = (int) 2166136261L;for (int i = 0, n = key.length(); i < n; i++) {hash = (hash ^ key.charAt(i)) * p;}hash += hash << 13;hash ^= hash >> 7;hash += hash << 3;hash ^= hash >> 17;hash += hash << 5;return ((int) hash & 0x7FFFFFFF) ;
}

5.3 测试

 @Testpublic void contextLoads() {//imei前缀String base = "86582003";//a,b,c,d,分别记录四组分到的imei个数int a = 0;int b = 0;int c = 0;int d = 0;for (int i = 0; i < 10000; i++) {//模拟15位的imei码String str = base + (int) (Math.random() * 9) * 10 + "" + (int) ((Math.random() * 9 + 1) * 100000);//模4 将FnvHash算法得到的固定结果分成四组int hash = FnvHash(str)%4;switch (hash){case 0:a++;break;case 1:b++;break;case 2:c++;break;case 3:d++;break;}}System.out.println("a:" + a);System.out.println("b:" + b);System.out.println("c:" + c);System.out.println("d:" + d);System.out.println("a+b+c+d:" + (a + b + c + d));}

5.5 多次实验结果

还比较满意,每次都比较均匀

a:2475
b:2526
c:2497
d:2502
a+b+c+d:10000
a:2494
b:2514
c:2485
d:2507
a+b+c+d:10000
a:2482
b:2486
c:2499
d:2533
a+b+c+d:10000

一致性Hash算法及Java实践相关推荐

  1. hash的算法 java_【数据结构与算法】一致性Hash算法及Java实践

    追求极致才能突破极限 一.案例背景 1.1 系统简介 首先看一下系统架构,方便解释: 页面给用户展示的功能就是,可以查看任何一台机器的某些属性(以下简称系统信息). 消息流程是,页面发起请求查看指定机 ...

  2. 对一致性Hash算法,Java代码实现的深入研究

    一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...

  3. 一致性Hash算法(JAVA版)(摘抄至五月的仓颉的博客)

    一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...

  4. java hash取模,一致性hash算法及其java实现

    目录 背景 随着业务系统越来越大,我们需要对API的访问进行更多的缓存,使用Redis是一个很好的解决方案. 但是单台Redis性能不足够且迟早要走向集群的,那么怎么才能良好的利用Redis集群来进行 ...

  5. java的hash算法实现_一致性Hash算法的Java实现详解

    package com.baijob.commonTools; import java.util.Collection; import java.util.SortedMap; import java ...

  6. 一致性Hash算法以及java实现

    目前我们很多时候都是在做分布式系统,但是我们需把客户端的请求均匀的分布到N个服务器中,一般我们可以考虑通过Object的HashCodeHash%N,通过取余,将客户端的请求分布到不同的的服务端.但是 ...

  7. hash oracle 分表_一致性Hash算法在数据库分表中的实践

    最近有一个项目,其中某个功能单表数据在可预估的未来达到了亿级,初步估算在90亿左右.与同事详细讨论后,决定采用一致性Hash算法来完成数据库的自动扩容和数据迁移.整个程序细节由我同事完成,我只是将其理 ...

  8. Java之一致性hash算法原理及实现

    为什么80%的码农都做不了架构师?>>>    一致性哈希算法是分布式系统中常用的算法. 比如,一个分布式的存储系统,要将数据存储到具体的节点上,如果采用普通的hash方法,将数据映 ...

  9. 一致性hash算法_(图文案例)一致性哈希算法详解 一点课堂(多岸教育)

    一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...

最新文章

  1. 可以不封神,但是不能不修炼——亚特兰蒂斯之神特斯拉的启示
  2. Django rest framework 基础
  3. HDU5706 GirlCat
  4. 学习笔记(47):Python实战编程-pack布局
  5. 将Wiremock集成到Spring Boot Java Web应用程序中以模拟外部依赖关系
  6. 旧文重发:苹果是怎么吃到的?
  7. 三调数据库及DLTB各个字段含义
  8. java插入数据库字符串拼接_java连接mysql数据库实现单条插入和批量插入
  9. ASP.NET 5 WebApi 返回 HttpResponseMessage
  10. 西农 生成树配置_华为交换机配置STP功能示例
  11. android chrome 无法下载,Android Chrome浏览器将支持下载暂停和取消
  12. eova1.4版本下拉框没有滚动条
  13. 2022,程序员的出路在哪里?
  14. linux网络接口是什么,网络接口是什么?What Is A Network Interface?--用Enki学Linux系列(1)...
  15. win11占用内存太高怎么解决?
  16. 什么是单页面应用开发?
  17. 网心科技获得深圳市“专精特新”中小企业认定
  18. Mysql学习之constraint/key/primary key/unique/foreign key/constraint的关系
  19. platform总线
  20. 巧用Option键,提升Mac工作效率(二)

热门文章

  1. 第四周毛概课学习心得
  2. php ajax二级联下拉菜单,ThinkPHP和Ajax 实现二级联动的下拉菜单
  3. Python爬虫高德地图全国各个城市POI并导出表格(PyCharm )结尾含源码地址
  4. Windows10下安装MXNet-走过的那些坑
  5. almon多项式_第5章 第2节 有限分布滞后模型.ppt
  6. 为什么 nginx 的性能如此牛逼?
  7. ApowerPDF垃圾
  8. 《苏格拉底回忆录》节选——劝戒人要自制
  9. 阿里云数据库购买流程和使用方法
  10. 安装Mathtype 7 在 word2016中,解决Word闪退问题