一致性哈希算法原理分析及实现

        一致性哈希算法常用于负载均衡中要求资源被均匀的分布到所有节点上,并且对资源的请求能快速路由到对应的节点上。具体的举两个场景的例子:

        1、MemCache集群,要求存储各种数据均匀的存到集群中的各个节点上,访问这些数据时能快速的路由到集群中对应存放该数据的节点上;并且要求增删节点对整个集群的影响很小,不至于有大的动荡造成整体负载的不稳定;

        2、RPC过程中服务提供者做N个节点的集群部署,为了能在服务上维护一些业务状态,希望同一种请求每次都落到同一台服务上。

        比如有{N0, N1, N2}三个节点,陆续有多个资源要分配到这三个节点上,如何尽可能均匀的分配到这些节点上?

        一致性哈希算法的思路为:先构造出一个长度为232整数环,根据N0-3的节点名称的hash值(分布为[0,232-1])放到这个环上。现在要存放资源,根据资源的Key的Hash值(也是分布为[0,232-1])值Haaa,在环上顺时针的找到离Haaa最近(第一个大于或等于Haaa)的一个节点,就建立了资源和节点的映射关系。

        

        以上图片引自http://www.cnblogs.com/xrq730/p/4948707.html。

        为什么要用环存储节点,并用hashKey顺时针寻找对应节点?

        我们分配节点最简单的办法是取余算法,即有3个节点,资源key=5, 5%3=2,选取N2,key=3,3%3=0,选取N0。虽然简单,但有个缺点,如果节点数增加或减少,就会有大量的key不命中,造成请求压力转移,可能对系统整体有很大的影响,甚至发生宕机危险。

        而一致性哈希算法增加或减少节点,只会引起少部分key不命中,如下图,增加一个Node4节点,只会将加粗部分的key值从Node1(10.0.0.0:91002)移到Node4(10.0.0.0:91003),对集群影响很小。

        以上图片引自http://www.cnblogs.com/xrq730/p/4948707.html

        Java实现中用什么表示Hash环好呢?经对比,用TreeMap的时间复杂度是O(logN),相对效率比较高,因为TreeMap使用了红黑树结构存储实体对象。

        Hash算法的选择上,首先我们考虑简单的String.HashCode()方法,这个算法的缺点是,相似的字符串如N1(10.0.0.0:91001),N2(10.0.0.0:91002),N3(10.0.0.0:91003),哈希值也很相近,造成的结果是节点在Hash环上分布很紧密,导致大部分Key值落到了N0上,节点资源分布不均。一般我们采用FNV1_32_HASH、KETAMA_HASH等算法,KETAMA_HASH是MemCache集群默认的实现方法,这些算法效果要好得多,会使N0,N1,N2的Hash值更均匀的分布在环上。

        我们用KETAMA_HASH算法实现一致性哈希(无虚拟节点方式),如下代码所示:        

 1 package com.example.demo.arithmetic;
 2 
 3 import java.io.UnsupportedEncodingException;
 4 import java.security.MessageDigest;
 5 import java.security.NoSuchAlgorithmException;
 6 import java.util.Arrays;
 7 import java.util.Map;
 8 import java.util.TreeMap;
 9 
10 /**
11  * Created by markcd on 2018/2/28.
12  */
13 public class ConsistentHashLoadBalanceNoVirtualNode {
14 
15     private TreeMap<Long, String> realNodes = new TreeMap<>();
16     private String[] nodes;
17 
18     public ConsistentHashLoadBalanceNoVirtualNode(String[] nodes){
19         this.nodes = Arrays.copyOf(nodes, nodes.length);
20         initalization();
21     }
22 
23     /**
24      * 初始化哈希环
25      * 循环计算每个node名称的哈希值,将其放入treeMap
26      */
27     private void initalization(){
28         for (String nodeName: nodes) {
29             realNodes.put(hash(nodeName, 0), nodeName);
30         }
31     }
32 
33     /**
34      * 根据资源key选择返回相应的节点名称
35      * @param key
36      * @return 节点名称
37      */
38     public String selectNode(String key){
39         Long hashOfKey = hash(key, 0);
40         if (! realNodes.containsKey(hashOfKey)) {
//ceilingEntry()的作用是得到比hashOfKey大的第一个Entry
41 Map.Entry<Long, String> entry = realNodes.ceilingEntry(hashOfKey); 42 if (entry != null) 43 return entry.getValue(); 44 else 45 return nodes[0]; 46 }else 47 return realNodes.get(hashOfKey); 48 } 49 50 private Long hash(String nodeName, int number) { 51 byte[] digest = md5(nodeName); 52 return (((long) (digest[3 + number * 4] & 0xFF) << 24) 53 | ((long) (digest[2 + number * 4] & 0xFF) << 16) 54 | ((long) (digest[1 + number * 4] & 0xFF) << 8) 55 | (digest[number * 4] & 0xFF)) 56 & 0xFFFFFFFFL; 57 } 58 59 /** 60 * md5加密 61 * 62 * @param str 63 * @return 64 */ 65 public byte[] md5(String str) { 66 try { 67 MessageDigest md = MessageDigest.getInstance("MD5"); 68 md.reset(); 69 md.update(str.getBytes("UTF-8")); 70 return md.digest(); 71 } catch (NoSuchAlgorithmException e) { 72 e.printStackTrace(); 73 return null; 74 } catch (UnsupportedEncodingException e) { 75 e.printStackTrace(); 76 return null; 77 } 78 } 79 80 private void printTreeNode(){ 81 if (realNodes != null && ! realNodes.isEmpty()){ 82 realNodes.forEach((hashKey, node) -> 83 System.out.println( 84 new StringBuffer(node) 85 .append(" ==> ") 86 .append(hashKey) 87 ) 88 ); 89 }else 90 System.out.println("Cycle is Empty"); 91 } 92 93 public static void main(String[] args){ 94 String[] nodes = new String[]{"192.168.2.1:8080", "192.168.2.2:8080", "192.168.2.3:8080", "192.168.2.4:8080"}; 95 ConsistentHashLoadBalanceNoVirtualNode consistentHash = new ConsistentHashLoadBalanceNoVirtualNode(nodes); 96 consistentHash.printTreeNode(); 97 } 98 }

 

        main()方法执行结果如下,可以看到,hash值分布的距离比较开阔。

        192.168.2.3:8080 ==> 1182102228

        192.168.2.4:8080 ==> 1563927337

        192.168.2.1:8080 ==> 2686712470

        192.168.2.2:8080 ==> 3540412423

        KETAMA_HASH解决了hash值分布不均的问题,但还存在一个问题,如下图,在没有Node3节点时,资源相对均匀的分布在{Node0,Node1,Node2}上。增加了Node3节点后,Node1到Node3节点中间的所有资源从Node2迁移到了Node3上。这样,Node0,Node1存储的资源多,Node2,Node3存储的资源少,资源分布不均匀。

        以上图片引自http://www.cnblogs.com/xrq730/p/4948707.html

        如何解决这个问题呢?我们引入虚拟节点概念,如将一个真实节点Node0映射成100个虚拟节点分布在Hash环上,与这100个虚拟节点根据KETAMA_HASH哈希环匹配的资源都存到真实节点Node0上。{Node0,Node1,Node2}以相同的方式拆分虚拟节点映射到Hash环上。当集群增加节点Node3时,在Hash环上增加Node3拆分的100个虚拟节点,这新增的100个虚拟节点更均匀的分布在了哈希环上,可能承担了{Node0,Node1,Node2}每个节点的部分资源,资源分布仍然保持均匀。

        每个真实节点应该拆分成多少个虚拟节点?数量要合适才能保证负载分布的均匀,有一个大致的规律,如下图所示,Y轴表示真实节点的数目,X轴表示需拆分的虚拟节点数目:

        

        真实节点越少,所需阐发的虚拟节点越多,比如有10个真实节点,每个节点所需拆分的虚拟节点个数可能是100~200个,才能达到真正的负载均衡。

        下面贴出使用了虚拟节点的算法实现:        

  1 package com.example.demo.arithmetic;
  2 
  3 import java.io.UnsupportedEncodingException;
  4 import java.security.MessageDigest;
  5 import java.security.NoSuchAlgorithmException;
  6 import java.util.LinkedList;
  7 import java.util.Map;
  8 import java.util.TreeMap;
  9 
 10 /**
 11  * Created by markcd on 2018/2/28.
 12  */
 13 public class ConsistentHashLoadBalance {
 14 
 15     private TreeMap<Long, String> virtualNodes = new TreeMap<>();
 16     private LinkedList<String> nodes;
//每个真实节点对应的虚拟节点数
17 private final int replicCnt; 18 19 public ConsistentHashLoadBalance(LinkedList<String> nodes, int replicCnt){ 20 this.nodes = nodes; 21 this.replicCnt = replicCnt; 22 initalization(); 23 } 24 25 /** 26 * 初始化哈希环 27 * 循环计算每个node名称的哈希值,将其放入treeMap 28 */ 29 private void initalization(){ 30 for (String nodeName: nodes) { 31 for (int i = 0; i < replicCnt/4; i++) { 32 String virtualNodeName = getNodeNameByIndex(nodeName, i); 33 for (int j = 0; j < 4; j++) { 34 virtualNodes.put(hash(virtualNodeName, j), nodeName); 35 } 36 } 37 } 38 } 39 40 private String getNodeNameByIndex(String nodeName, int index){ 41 return new StringBuffer(nodeName) 42 .append("&&") 43 .append(index) 44 .toString(); 45 } 46 47 /** 48 * 根据资源key选择返回相应的节点名称 49 * @param key 50 * @return 节点名称 51 */ 52 public String selectNode(String key){ 53 Long hashOfKey = hash(key, 0); 54 if (! virtualNodes.containsKey(hashOfKey)) { 55 Map.Entry<Long, String> entry = virtualNodes.ceilingEntry(hashOfKey); 56 if (entry != null) 57 return entry.getValue(); 58 else 59 return nodes.getFirst(); 60 }else 61 return virtualNodes.get(hashOfKey); 62 } 63 64 private Long hash(String nodeName, int number) { 65 byte[] digest = md5(nodeName); 66 return (((long) (digest[3 + number * 4] & 0xFF) << 24) 67 | ((long) (digest[2 + number * 4] & 0xFF) << 16) 68 | ((long) (digest[1 + number * 4] & 0xFF) << 8) 69 | (digest[number * 4] & 0xFF)) 70 & 0xFFFFFFFFL; 71 } 72 73 /** 74 * md5加密 75 * 76 * @param str 77 * @return 78 */ 79 public byte[] md5(String str) { 80 try { 81 MessageDigest md = MessageDigest.getInstance("MD5"); 82 md.reset(); 83 md.update(str.getBytes("UTF-8")); 84 return md.digest(); 85 } catch (NoSuchAlgorithmException e) { 86 e.printStackTrace(); 87 return null; 88 } catch (UnsupportedEncodingException e) { 89 e.printStackTrace(); 90 return null; 91 } 92 } 93 94 public void addNode(String node){ 95 nodes.add(node); 96 String virtualNodeName = getNodeNameByIndex(node, 0); 97 for (int i = 0; i < replicCnt/4; i++) { 98 for (int j = 0; j < 4; j++) { 99 virtualNodes.put(hash(virtualNodeName, j), node); 100 } 101 } 102 } 103 104 public void removeNode(String node){ 105 nodes.remove(node); 106 String virtualNodeName = getNodeNameByIndex(node, 0); 107 for (int i = 0; i < replicCnt/4; i++) { 108 for (int j = 0; j < 4; j++) { 109 virtualNodes.remove(hash(virtualNodeName, j), node); 110 } 111 } 112 } 113 114 private void printTreeNode(){ 115 if (virtualNodes != null && ! virtualNodes.isEmpty()){ 116 virtualNodes.forEach((hashKey, node) -> 117 System.out.println( 118 new StringBuffer(node) 119 .append(" ==> ") 120 .append(hashKey) 121 ) 122 ); 123 }else 124 System.out.println("Cycle is Empty"); 125 } 126 127 public static void main(String[] args){ 128 LinkedList<String> nodes = new LinkedList<>(); 129 nodes.add("192.168.2.1:8080"); 130 nodes.add("192.168.2.2:8080"); 131 nodes.add("192.168.2.3:8080"); 132 nodes.add("192.168.2.4:8080"); 133 ConsistentHashLoadBalance consistentHash = new ConsistentHashLoadBalance(nodes, 160); 134 consistentHash.printTreeNode(); 135 } 136 }

         以上main方法执行的结果如下:

         192.168.2.4:8080 ==> 18075595
         192.168.2.1:8080 ==> 18286704
         192.168.2.1:8080 ==> 35659769
         192.168.2.2:8080 ==> 43448858
         192.168.2.1:8080 ==> 44075453
         192.168.2.3:8080 ==> 47625378
         ........(由于内容过多,不做全部展示)

 

        至此哈希一致性算法的原理和实现描述完毕,欢迎大家讨论,如有不当的地方欢迎大家提出异议。

转载于:https://www.cnblogs.com/markcd/p/8476237.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/354076.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jsf集成spring_JSF – PrimeFaces和Hibernate集成项目

jsf集成spring本文介绍了如何使用JSF&#xff0c;PrimeFaces和Hibernate开发项目。 下面是一个示例应用程序&#xff1a; 二手技术&#xff1a; JDK 1.6.0_21 Maven的3.0.2 JSF 2.0.3 PrimeFaces 2.2.1 Hibernate3.6.7 MySQL Java连接器5.1.17 MySQL 5.5.8 Apache Tomcat 7.…

帝国 loginjs.php,帝国cms 6.6 后台拿shell

时间:2013-02-27来源:源码库 作者:源码库 文章热度:℃漏洞作者&#xff1a; 付弘雪提交时间&#xff1a; 2013-01-21公开时间&#xff1a; 2013-01-21漏洞类型&#xff1a; 文件上传导致任意代码执行简要描述&#xff1a;帝国cms 6.6版本后台拿shell 比网上流行的方法简单很多由…

带有JSF,Servlet和CDI的DynamicReports和JasperReports

在此示例中&#xff0c;我将展示如何将DynamicReport和JasperReports与Servlet和CDI集成。 工具&#xff1a; TIBCO Jaspersoft Studio-6.0.4。最终版 Eclipse Luna服务版本2&#xff08;4.4.2&#xff09;。 WildFly 8.x应用程序服务器。 这是Eclipse上项目层次结构的屏幕…

swing 聊天气泡背景_Java Swing中的聊天气泡

swing 聊天气泡背景本文将向您解释“如何在Java swing应用程序中绘制聊天气泡&#xff1f;” 聊天气泡与呼出或提示气泡相同。 今天&#xff0c;大多数聊天应用程序都以这种格式显示转换&#xff0c;因此本文将帮助您在用Java swing创建的桌面应用程序中执行相同的操作。 以下课…

jdk7与jdk8环境共存与切换

1&#xff0c;先安装jdk7,配置环境变量JAVA_HOME,然后安装jdk8。 2&#xff0c;安装jdk8后&#xff0c;JAVA_HOME指向未做修改&#xff0c;执行java -version显示还是以前的jdk7版本信息&#xff0c; 3&#xff0c;接下来我们配置环境变量JAVA_HOME,发现配置jdk7的路径,或者配置…

PHP培训选云和数据,送给云和数据郑州分中心PHP培训班全体学员的祝福

在云和数据数百个日日夜夜&#xff0c;郑州PHP培训班的学员们一起成长&#xff0c;从青春懵懂到成熟稳重&#xff0c;从羞涩内敛到侃侃而谈&#xff0c;他们用奋斗和拼搏共同刻画了人生中一段难忘的回忆。12月12日&#xff0c;云和数据郑州分中心PHP培训班毕业典礼&#xff0c;…

cyclicbarrier_Java并发– CyclicBarrier示例

cyclicbarrierJava中的CyclicBarrier是JDK 5中在java.util.Concurrent包上引入的同步器&#xff0c;以及其他并发实用程序&#xff0c;例如Counting Semaphore &#xff0c; BlockingQueue &#xff0c; ConcurrentHashMap等。CyclicBarrier与CountDownLatch类似&#xff0c;我…

php队列失败是指什么,队列是什么意思

队列是一种特殊的线性表。它只允许在表的前端(front)进行删除操作&#xff0c;而在表的后端(rear)进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表&#xff1b;进行插入操作的端称为队尾&#xff0c;进行删除操作的端称为队头&#xff1b;队列中没有…

从In Memory Data Grid,Apache Ignite快速入门

IMDG或内存数据网格不是内存中关系数据库&#xff0c;NOSQL数据库或关系数据库。 它是另一种软件数据存储库。 数据模型分布在单个位置或多个位置的许多服务器上。 这种分布称为数据结构。 这种分布式模型被称为“无共享”架构。 IMDG具有以下特征&#xff1a; 所有服务器可以…

91.91p10.space v.php,luogu P1091 合唱队形

任务计划推了很久才做www从两头开始的单调上升队列没啥可说的#include#includeusing namespace std;#define maxn 110int a[maxn];int f[2][maxn];int ans;int main() {int n;scanf("%d",&n);for(int i 1; i < n; i)scanf("%d",&a[i]);a[0] a…

BZOJ 2097 [Usaco2010 Dec]Exercise 奶牛健美操

【题意】 给出一棵树。现在可以在树中删去m条边&#xff0c;使它变成m1棵树。要求最小化树的直径的最大值。 【题解】 二分答案。$Check$的时候用$DP$&#xff0c;记录当前节点每个儿子的直径$v[i]$&#xff0c;如果$v[i]1>mid$&#xff0c;那么就断掉连向儿子的这条边。如果…

daterangepicker双日历插件的使用

今天主要是由于项目的需要&#xff0c;做了一个daterangepicker双日历插件&#xff0c;做出来的效果如下&#xff1a; 个人感觉这个daterangepicker双日历插件很好用&#xff0c;并且实现起来也不是很麻烦&#xff0c;我是根据它的官方文档去写的&#xff0c;并将Bootstrap也整…

php 递归展现城市信息,PHP 递归兑现层级树状展现数据

PHP 递归实现层级树状展现数据?$arr[id],fid > $arr[fid],name > $arr[name],);}// 将数据按照缩进简单排列 见图1function data2arr($tree, $rootId 0, $level 0) {foreach($tree as $leaf) {if($leaf[fid] $rootId) {echo str_repeat( , $level) . $leaf[id] . .…

牛客网 2018年全国多校算法寒假训练营练习比赛(第五场) H.Tree Recovery-完全版线段树(区间更新、区间求和)...

H.Tree Recovery时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒空间限制&#xff1a;C/C 131072K&#xff0c;其他语言262144K64bit IO Format: %lld链接&#xff1a;https://www.nowcoder.com/acm/contest/77/H来源&#xff1a;牛客网题目描述 You have N integers, A1,…

微信登陆超时 重新登录_重新登录:重新登录

微信登陆超时 重新登录嗨&#xff0c;我再次感到非常高兴&#xff0c;认为日志记录是任何应用程序设计和开发的固有部分。 我是坚强的基础知识的忠实拥护者&#xff0c;以我的拙见&#xff0c;日志记录是任何企业级应用程序中经常被忽略但基本的关键要素之一。 我已经写在此之前…

快速幂矩阵快速幂

快速幂 题目链接&#xff1a;https://www.luogu.org/problemnew/show/P1226 快速幂用了二分的思想&#xff0c;即将\(a^{b}\)的指数b不断分解成二进制的形式&#xff0c;然后相乘累加起来&#xff0c;就是用\(a^{b/2}a^{b/2}\)去求\(a{^b}\)。 例如:\(a^{11}a^{(2^02^12^3)}\)…

前端项目里常见的十种报错及其解决办法

错误一&#xff1a;Uncaught TypeError: Cannot set property onclick of nullat operate.js:86图片.png原因&#xff1a;当js文件放在head里面时&#xff0c;如果绑定了onclick事件&#xff0c;就会出现这样的错误&#xff0c;是因为W3School的写法是浏览器先加载完按钮节点才…

监控oracle数据io,Prometheus监控Oracle数据库

背景本文简单介绍下&#xff0c;Prometheus如何通过exporters监控Oracle数据库&#xff0c;以及应该注意哪些指标。oracledb_exporteroracledb_exporter是一个连接到Oracle数据库并生成Prometheus metrics的应用程序&#xff0c;设置展示下如何安装和设置oracledb_exporter&…

php workman 多线程,workerman如何多线程

Workerman有一个依赖pthreads扩展的MT多线程版本&#xff0c;但是由于pthreads扩展还不够稳定&#xff0c;所以这个Workerman多线程版本已经不再维护。 (推荐学习&#xff1a; workerman教程)workerman\mqtt 是一个基于workerman的异步mqtt 客户端库&#xff0c;可用于接收或者…

js Object的属性 Configurable,Enumerable,Writable,Value,Getter,Setter

对象的数据属性 Configurable,Enumerable,Writable,Value var person {} Object.defineProperty(person,name,{configurable:false,//能否使用delete、能否需改属性特性、或能否修改访问器属性、&#xff0c;false为不可重新定义&#xff0c;默认值为true enumerable:false,//…