一致性hash

前言

很多人估计都听过哈希,顾名思义,一般程序的直接反映就是做映射的嘛,哈希算法,当然这不是今天本文所讲的重点,今天主要所讲的是另外一个名词,一致性哈希算法,光从字面上的意思想,这一定是对于原有算法的一个改进了。

Hash

我们先从最简单的hash方法开始说起,哈希方法可以有很多种类型,字符串哈希,数值类型的哈希,实体类的哈希,其实这些都可以统称为对象的哈希,用一个方法就可以表示就是hashcode()方法,在Java里反正是存在的,其他的语言是不是这么写我确实还不太清楚。一般哈希方法常用来做一种关系映射,然后进行分配的,最终起到一个均匀分配,负载均衡的目的,这个在很多开源代码中都会有类似这样的实现。但是这其中会存在一个隐患,举个例子,若干个对象分配到若干个机器节点上,首先使用规范的哈希映射再%N,效果还不错,差不多均匀分配,但是突然有一天有个机器挂了,那么问题大了,很多的映射就会不准了,因为N变了。为了保持住原有的一致性,因此提出了一致性哈希算法。

一致性哈希

一致性哈希算法的提出是在由麻省理工学院在1997年提出的,旨在解决因特网中的Hot Spot热点问题的。一致性哈希算法在不改变原有的哈希算法的前提下,提出了哈希环的概念,将对象和机器映射到一个0~2的31次方的数值,然后假想数字是一个从小到大的患空间,对象的分配方式是以顺时针方向,离对象最近的机器就是对象所分配的机器节点。用图形表示就是下面这个样子:


这样就好理解多了吧。这样就能够很好的解决之前的问题了,机器的删除和添加只会影响到个别节点,其余的对象分配依然是不变的。

虚拟节点

上述的哈希环的方式看起来已经非常的完美了,不过还是有可能会造成一个问题,节点分配不均衡,导致对象的分配不均衡,经过多次的节点的添加,删除,可能左半环的节点数量,明显多于右半边的节点数,这个时候有没有什么改进的方案呢,答案是有,用虚拟节点表示,相当于每个节点有replication副本数的概念,每个机器的副本可以用ip或机器名+数字后缀的形式进行哈希映射,详细可以参见后面我的代码实现。效果图是下面这个样子:


代码实现

全部代码链接在此:https://github.com/linyiqun/lyq-algorithms-lib/tree/master/ConsistentHash

下面给出核心的算法实现ConsistentHashTool.java(hashcode映射时偶尔会有越界的情况发生):

[java]  view plain  copy
 print ?
  1. package ConsistentHash;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7. import java.text.MessageFormat;  
  8. import java.util.ArrayList;  
  9. import java.util.Collections;  
  10. import java.util.HashMap;  
  11. import java.util.Map;  
  12. import java.util.Random;  
  13.   
  14. /** 
  15.  * 一致性哈希算法工具类 
  16.  *  
  17.  * @author lyq 
  18.  *  
  19.  */  
  20. public class ConsistentHashTool {  
  21.     // 机器节点信息文件地址  
  22.     private String filePath;  
  23.     // 每个节点虚拟节点的个数  
  24.     private int virtualNodeNum;  
  25.     // 测试实体对象列表  
  26.     private ArrayList<Entity> entityLists;  
  27.     // 节点列表  
  28.     private ArrayList<Node> totalNodes;  
  29.     // 结果分配列表  
  30.     private HashMap<Entity, Node> assignedResult;  
  31.   
  32.     public ConsistentHashTool(String filePath, int virtualNodeNum,  
  33.             ArrayList<Entity> entityLists) {  
  34.         this.filePath = filePath;  
  35.         this.virtualNodeNum = virtualNodeNum;  
  36.         this.entityLists = entityLists;  
  37.   
  38.         readDataFile();  
  39.     }  
  40.   
  41.     /** 
  42.      * 从文件中读取数据 
  43.      */  
  44.     private void readDataFile() {  
  45.         File file = new File(filePath);  
  46.         ArrayList<String[]> dataArray = new ArrayList<String[]>();  
  47.   
  48.         try {  
  49.             BufferedReader in = new BufferedReader(new FileReader(file));  
  50.             String str;  
  51.             String[] tempArray;  
  52.             while ((str = in.readLine()) != null) {  
  53.                 tempArray = str.split(" ");  
  54.                 dataArray.add(tempArray);  
  55.             }  
  56.             in.close();  
  57.         } catch (IOException e) {  
  58.             e.getStackTrace();  
  59.         }  
  60.   
  61.         Node node;  
  62.         String name;  
  63.         String ip;  
  64.         long hashValue;  
  65.   
  66.         this.totalNodes = new ArrayList<>();  
  67.         // 解析出每行的节点名称和ip地址  
  68.         for (String[] array : dataArray) {  
  69.             name = array[0];  
  70.             ip = array[1];  
  71.   
  72.             // 根据IP地址进行hash映射  
  73.             hashValue = ip.hashCode();  
  74.             node = new Node(name, ip, hashValue);  
  75.             this.totalNodes.add(node);  
  76.         }  
  77.   
  78.         // 对节点按照hashValue值进行升序排列  
  79.         Collections.sort(this.totalNodes);  
  80.     }  
  81.   
  82.     /** 
  83.      * 哈希算法分配对象实例 
  84.      */  
  85.     public void hashAssigned() {  
  86.         Node desNode;  
  87.   
  88.         this.assignedResult = new HashMap<>();  
  89.         for (Entity e : this.entityLists) {  
  90.             desNode = selectDesNode(e, this.totalNodes);  
  91.   
  92.             this.assignedResult.put(e, desNode);  
  93.         }  
  94.   
  95.         outPutAssginedResult();  
  96.     }  
  97.   
  98.     /** 
  99.      * 通过虚拟节点的哈希算法分配 
  100.      */  
  101.     public void hashAssignedByVirtualNode() {  
  102.         String name;  
  103.         String ip;  
  104.         long hashValue;  
  105.   
  106.         // 用以生成随机数数字后缀  
  107.         Random random;  
  108.         Node node;  
  109.         ArrayList<Node> virtualNodes;  
  110.   
  111.         random = new Random();  
  112.         // 创建虚拟节点  
  113.         virtualNodes = new ArrayList<>();  
  114.         for (Node n : this.totalNodes) {  
  115.             name = n.name;  
  116.             ip = n.ip;  
  117.   
  118.             // 复制虚拟节点个数  
  119.             for (int i = 0; i < this.virtualNodeNum; i++) {  
  120.                 // 虚拟节点的哈希值用ip+数字后缀的形式生成  
  121.                 hashValue = (ip + "#" + (random.nextInt(1000) + 1)).hashCode();  
  122.   
  123.                 node = new Node(name, ip, hashValue);  
  124.                 virtualNodes.add(node);  
  125.             }  
  126.         }  
  127.         // 进行升序排序  
  128.         Collections.sort(virtualNodes);  
  129.   
  130.         // 哈希算法分配节点  
  131.         Node desNode;  
  132.         this.assignedResult = new HashMap<>();  
  133.         for (Entity e : this.entityLists) {  
  134.             desNode = selectDesNode(e, virtualNodes);  
  135.   
  136.             this.assignedResult.put(e, desNode);  
  137.         }  
  138.   
  139.         outPutAssginedResult();  
  140.     }  
  141.   
  142.     /** 
  143.      * 在哈希环中寻找归属的节点 
  144.      *  
  145.      * @param entity 
  146.      *            待分配的实体 
  147.      * @param nodeList 
  148.      *            节点列表 
  149.      * @return 
  150.      */  
  151.     private Node selectDesNode(Entity entity, ArrayList<Node> nodeList) {  
  152.         Node desNode;  
  153.         int hashValue;  
  154.   
  155.         desNode = null;  
  156.         hashValue = entity.hashCode();  
  157.   
  158.         for (Node n : nodeList) {  
  159.             // 按照顺时针方向,选择一个距离最近的哈希值节点  
  160.             if (n.hashValue > hashValue) {  
  161.                 desNode = n;  
  162.                 break;  
  163.             }  
  164.         }  
  165.   
  166.         // 如果没有找到说明已经超过最大的hashValue,按照环状,被划分到第一个  
  167.         if (desNode == null) {  
  168.             desNode = nodeList.get(0);  
  169.         }  
  170.   
  171.         return desNode;  
  172.     }  
  173.   
  174.     /** 
  175.      * 输出分配结果 
  176.      */  
  177.     private void outPutAssginedResult() {  
  178.         Entity e;  
  179.         Node n;  
  180.   
  181.         for (Map.Entry<Entity, Node> entry : this.assignedResult.entrySet()) {  
  182.             e = entry.getKey();  
  183.             n = entry.getValue();  
  184.   
  185.             System.out.println(MessageFormat.format("实体{0}被分配到了节点({1}, {2})",  
  186.                     e.name, n.name, n.ip));  
  187.         }  
  188.     }  
  189. }  

参考文献

百度百科

http://blog.csdn.net/cywosp/article/details/23397179/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值