第十一章 自己实现一致性hash算法

关于一致性hash算法的意义以及其相对于简单求余法(除数求余法)的好处,查看第六章 memcached剖析

注意:真实的hash环的数据结构是二叉树,这里为了简便使用了列表List

1、一致性hash算法的使用地方

  • memcached服务器
  • Jedis分片机制

2、真实服务器节点没有虚拟化的一致性hash算法实现

ServerNode:真实服务器节点

 1 package hash;
 2
 3 /**
 4  * server节点
 5  */
 6 public class ServerNode {
 7     private String serverName;
 8     private long serverHash;
 9
10     public String getServerName() {
11         return serverName;
12     }
13
14     public void setServerName(String serverName) {
15         this.serverName = serverName;
16     }
17
18     public long getServerHash() {
19         return serverHash;
20     }
21
22     public void setServerHash(long serverHash) {
23         this.serverHash = serverHash;
24     }
25
26     /**
27      * 下边重写hashcode()和equals()是为了在删除节点的时候只根据传入的serverName删除即可
28      */
29     @Override
30     public int hashCode() {
31         final int prime = 31;
32         int result = 1;
33         result = prime * result
34                 + ((serverName == null) ? 0 : serverName.hashCode());
35         return result;
36     }
37
38     @Override
39     public boolean equals(Object obj) {
40         if (this == obj)
41             return true;
42         if (obj == null)
43             return false;
44         if (getClass() != obj.getClass())
45             return false;
46         ServerNode other = (ServerNode) obj;
47         if (serverName == null) {
48             if (other.serverName != null)
49                 return false;
50         } else if (!serverName.equals(other.serverName))
51             return false;
52         return true;
53     }
54
55
56 }

注意:

  • serverName可以自己取名,这里取名为"ip:port"
  • 对于hashCode()和equals()方法的重写仅仅是为了删除服务器节点的时候,只根据serverName就可以删除,而不需要再计算服务器节点的hash值

ServerComparator:真实服务器比较器

 1 package hash;
 2
 3 import java.util.Comparator;
 4
 5 /**
 6  * 服务器排序比较器
 7  */
 8 public class ServerComparator implements Comparator<ServerNode> {
 9
10     public int compare(ServerNode node1, ServerNode node2) {
11         if(node1.getServerHash() <= node2.getServerHash()) {
12             return -1;//node1<node2
13         }
14         return 1;//node1>node2
15     }
16
17 }

注意:

  • 关于java的比较器,有两种:(后者用的多一些)

    • javabean实现comparable接口,实现compareTo()方法
    • 另外建一个类实现comparator接口,实现其中的compare()方法

ConsistentHash:一致性hash实现类

  1 package hash;
  2
  3 import java.util.ArrayList;
  4 import java.util.Collections;
  5 import java.util.List;
  6 import java.util.zip.CRC32;
  7
  8 /**
  9  * 一致性hash实现(数据结构:list)(服务器没有虚拟化)
 10  * 一致性hash的真正数据结构是二叉树
 11  */
 12 public class ConsistentHash {
 13     private List<ServerNode> servers = new ArrayList<ServerNode>();//存放服务器
 14
 15     /** 计算服务器和存储的键的hash值 */
 16     public long hash(String str){
 17         CRC32 crc32 = new CRC32();
 18         crc32.update(str.getBytes());
 19         return crc32.getValue();
 20     }
 21
 22     /**
 23      * 添加server到环上
 24      * @param serverName ip:port
 25      */
 26     public void addServer(String serverName){
 27
 28         ServerNode node = new ServerNode();
 29         node.setServerName(serverName);
 30         node.setServerHash(hash(serverName));
 31
 32         servers.add(node);
 33         Collections.sort(servers, new ServerComparator());
 34     }
 35
 36     /**
 37      * 从环上删除server节点
 38      */
 39     public void deleteServer(String serverName){
 40
 41         ServerNode node = new ServerNode();
 42         node.setServerName(serverName);
 43
 44         servers.remove(node);
 45     }
 46
 47     /**
 48      * 获取一个缓存key应该存放的位置
 49      * @param cachekey 缓存的key
 50      * @return 缓存的服务器节点
 51      */
 52     public ServerNode getServer(String cachekey){
 53         long keyHash = hash(cachekey);
 54
 55         for(ServerNode node : servers){
 56             if(keyHash<=node.getServerHash()){
 57                 return node;
 58             }
 59         }
 60
 61         return servers.get(0);//如果node没有合适放置位置,放在第一台服务器上去
 62     }
 63
 64     /****************测试*******************/
 65     public void printServers(){
 66         for(ServerNode server : servers){
 67             System.out.println(server.getServerName()+"-->"+server.getServerHash());
 68         }
 69     }
 70
 71     public static void main(String[] args) {
 72         ConsistentHash ch = new ConsistentHash();
 73         ch.addServer("127.0.0.1:11211");
 74         ch.addServer("127.0.0.1:11212");
 75         ch.addServer("127.0.0.2:11211");
 76         ch.addServer("127.0.0.2:11212");
 77
 78         ch.printServers();
 79
 80         ServerNode node = ch.getServer("hello");
 81         System.out.println(ch.hash("hello")+"-->"+node.getServerName()+"-->"+node.getServerHash());
 82
 83         ServerNode node2 = ch.getServer("name");
 84         System.out.println(ch.hash("name")+"-->"+node2.getServerName()+"-->"+node2.getServerHash());
 85
 86         ServerNode node3 = ch.getServer("a");
 87         System.out.println(ch.hash("a")+"-->"+node3.getServerName()+"-->"+node3.getServerHash());
 88
 89         /********************删除节点*********************/
 90         ch.deleteServer("127.0.0.1:11212");
 91         ch.printServers();
 92
 93         ServerNode node0 = ch.getServer("hello");
 94         System.out.println(ch.hash("hello")+"-->"+node0.getServerName()+"-->"+node0.getServerHash());
 95
 96         ServerNode node02 = ch.getServer("name");
 97         System.out.println(ch.hash("name")+"-->"+node02.getServerName()+"-->"+node02.getServerHash());
 98
 99         ServerNode node03 = ch.getServer("a");
100         System.out.println(ch.hash("a")+"-->"+node03.getServerName()+"-->"+node03.getServerHash());
101
102     }
103 }

注意:

  • 在计算服务器节点和存储的key的hash值的时候,不仅仅可以使用crc32算法,还可以使用MD5算法等等,只要是最后得出的结果是一个>=0&&<=232的数就好
  • 在这个实现中,并没有将真实服务器节点进行虚拟化

3、真实服务器节点虚拟化后的一致性hash算法实现

为什么要虚拟化,查看第六章 memcached剖析 ,这里只列出几条原因:

  • 在memcached服务器较少的情况下,很难平均的分布到hash环上,这样就会造成负载不均衡--引入虚拟化节点,可以解决这个问题
  • 当一台memcached宕机时,其原先所承受的压力全部给了其下一个节点,为了将其原先所承受的压力尽可能的分布给所有剩余的memcached节点,引入虚拟化节点可以达到这个目的
  • 当新添加了一台memcached服务器server1时,server1只会缓解其中的一台服务器(即server1插入环后,server1的下一个节点)的压力,为了可以让server1尽可能的缓解所有的memcached服务器的压力,引入虚拟节点可以达到这个目的

VirtualServerNode:虚拟节点

 1 package hash2;
 2
 3 /**
 4  * 虚拟节点
 5  */
 6 public class VirtualServerNode {
 7     private String serverName;//真实节点名称
 8     private long virtualServerHash;//虚拟节点hash
 9
10     public String getServerName() {
11         return serverName;
12     }
13
14     public void setServerName(String serverName) {
15         this.serverName = serverName;
16     }
17
18     public long getVirtualServerHash() {
19         return virtualServerHash;
20     }
21
22     public void setVirtualServerHash(long virtualServerHash) {
23         this.virtualServerHash = virtualServerHash;
24     }
25
26 }

注意:

  • 该类中的serverName是该虚拟节点对应的真实节点的名称,这里就是"ip:port"
  • 真正的虚拟节点的名称是serverName-i(其中,i是0~virtualCount的整数值),这一块儿请查看ConsistentHashWithVirtualNode的addServer(String serverName)

VirtualServerComparator:虚拟节点比较器

 1 package hash2;
 2
 3 import java.util.Comparator;
 4
 5 /**
 6  * 虚拟节点比较器
 7  */
 8 public class VirtualServerComparator implements Comparator<VirtualServerNode> {
 9
10     public int compare(VirtualServerNode node1, VirtualServerNode node2) {
11         if(node1.getVirtualServerHash() <= node2.getVirtualServerHash()) {
12             return -1;
13         }
14         return 1;
15     }
16
17 }

ConsistentHashWithVirtualNode:真实节点虚拟化后的一致性hash算法

  1 package hash2;
  2
  3 import java.util.ArrayList;
  4 import java.util.Collections;
  5 import java.util.List;
  6 import java.util.zip.CRC32;
  7
  8 /**
  9  * 具有虚拟节点的一致性hash实现(数据结构:list)
 10  * 一致性hash的真正数据结构是二叉树
 11  */
 12 public class ConsistentHashWithVirtualNode {
 13     private List<VirtualServerNode> virtualServers = new ArrayList<VirtualServerNode>();//存放虚拟节点
 14     private static final int virtualCount = 8;//每个真实节点虚拟成8个虚拟节点
 15
 16     /** 计算服务器和存储的键的hash值 */
 17     public long hash(String str){
 18         CRC32 crc32 = new CRC32();
 19         crc32.update(str.getBytes());
 20         return crc32.getValue();
 21     }
 22
 23     /**
 24      * 添加server的虚拟节点到环上
 25      * @param serverName ip:port
 26      */
 27     public void addServer(String serverName){
 28
 29         for(int count=0;count<virtualCount;count++){
 30             VirtualServerNode node = new VirtualServerNode();
 31             node.setServerName(serverName);
 32             node.setVirtualServerHash(hash(serverName+"-"+count));//虚拟节点的名字:serverName+"-"+count
 33             virtualServers.add(node);
 34         }
 35
 36         Collections.sort(virtualServers, new VirtualServerComparator());
 37     }
 38
 39     /**
 40      * 从环上删除server节点(需要删除所有的该server节点对应的虚拟节点)
 41      */
 42     public void deleteServer(String serverName){
 43
 44         /*
 45          * 在这种删除的时候,会出现java.util.ConcurrentModificationException
 46          * 这是因为此处的遍历方式为使用ArrayList内部类Itr进行遍历,
 47          * 在遍历的过程中发生了remove、add等操作,导致modCount发生了变化,
 48          * 产生并发修改异常,
 49          * 可以使用下边的那一种方式来进行遍历(遍历方式不是Itr),
 50          * 再这样的遍历过程中,add和remove都是没有问题的
 51          */
 52         /*for(VirtualServerNode node : virtualServers){
 53             if(node.getServerName().equals(serverName)){
 54                 virtualServers.remove(node);
 55             }
 56         }*/
 57         for(int i=0;i<virtualServers.size();i++) {
 58             VirtualServerNode node = virtualServers.get(i);
 59             if(node.getServerName().equals(serverName)) {
 60                 virtualServers.remove(node);
 61             }
 62         }
 63
 64     }
 65
 66     /**
 67      * 获取一个缓存key应该存放的位置
 68      * @param cachekey 缓存的key
 69      * @return 缓存的服务器节点
 70      */
 71     public VirtualServerNode getServer(String cachekey){
 72         long keyHash = hash(cachekey);
 73
 74         for(VirtualServerNode node : virtualServers){
 75             if(keyHash<=node.getVirtualServerHash()){
 76                 return node;
 77             }
 78         }
 79
 80         return virtualServers.get(0);//如果node没有合适放置位置,放在第一台服务器上去
 81     }
 82
 83     /****************测试*******************/
 84     public void printServers(){
 85         for(VirtualServerNode server : virtualServers){
 86             System.out.println(server.getServerName()+"-->"+server.getVirtualServerHash());
 87         }
 88     }
 89
 90     public static void main(String[] args) {
 91         ConsistentHashWithVirtualNode ch = new ConsistentHashWithVirtualNode();
 92         ch.addServer("127.0.0.1:11211");
 93         ch.addServer("127.0.0.1:11212");
 94         ch.addServer("127.0.0.2:11211");
 95         ch.addServer("127.0.0.2:11212");
 96
 97         ch.printServers();
 98
 99         VirtualServerNode node = ch.getServer("hello");
100         System.out.println(ch.hash("hello")+"-->"+node.getServerName()+"-->"+node.getVirtualServerHash());
101
102         VirtualServerNode node2 = ch.getServer("name");
103         System.out.println(ch.hash("name")+"-->"+node2.getServerName()+"-->"+node2.getVirtualServerHash());
104
105         VirtualServerNode node3 = ch.getServer("a");
106         System.out.println(ch.hash("a")+"-->"+node3.getServerName()+"-->"+node3.getVirtualServerHash());
107
108         /*********************删除节点之后**********************/
109         ch.deleteServer("127.0.0.1:11212");
110         ch.printServers();
111
112         VirtualServerNode node0 = ch.getServer("hello");
113         System.out.println(ch.hash("hello")+"-->"+node0.getServerName()+"-->"+node0.getVirtualServerHash());
114
115         VirtualServerNode node02 = ch.getServer("name");
116         System.out.println(ch.hash("name")+"-->"+node02.getServerName()+"-->"+node02.getVirtualServerHash());
117
118         VirtualServerNode node03 = ch.getServer("a");
119         System.out.println(ch.hash("a")+"-->"+node03.getServerName()+"-->"+node03.getVirtualServerHash());
120
121     }
122 }

注意:

  • 在实际操作中,一台memcached服务器虚拟成150台比较合适(100~200)
  • 从环上删除节点的算法写的较差,但是考虑到删除节点的操作在实际使用中用的比较少(宕机比较少,人为的删除节点也较少),也无所谓
  • 删除节点的时候,注意使用foreach语法糖去遍历的时候,在遍历的过程中不可以做删除、增加操作,否则会抛出并发修改异常,具体的原因见注释和第二章 ArrayList源码解析;想要实现在遍历的过程中进行删除、增加操作,使用简单for循环,见如上代码
时间: 2024-07-28 17:57:12

第十一章 自己实现一致性hash算法的相关文章

【转载】对一致性Hash算法,Java代码实现的深入研究

原文地址:http://www.cnblogs.com/xrq730/p/5186728.html 一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性Hash算法的算法原理做了详细的解读. 算法的具体原理这里再次贴上: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这

一致性hash算法

一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用. 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Balance):平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用.很多哈希算法都能够满足

分布式memcached学习(四)&mdash;&mdash; 一致性hash算法原理

    分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几个概念. 分布式 分布式(distributed)是指在多台不同的服务器中部署不同的服务模块,通过远程调用协同工作,对外提供服务. 以一个航班订票系统为例,这个航班订票系统有航班预定.网上值机.旅客信息管理.订单管理.运价计算等服务模块.现在要以集中式(集群,cluster)和分布

分布式算法(一致性Hash算法)

一.分布式算法 在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括: 轮循算法(Round Robin).哈希算法(HASH).最少连接算法(Least Connection).响应速度算法(Response Time).加权法(Weighted )等.其中哈希算法是最为常用的算法. 典型的应用场景是: 有N台服务器提供缓存服务,需要对服务器进行负载均衡,将请求平均分发到每台服务器上,每台机器负责1/N的服务. 常用的算法是对hash结果取余数 (hash() mod N ):对机器编号

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

一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法和一致性Hash算法的算法原理做了详细的解读. 算法的具体原理这里再次贴上: 先构造一个长度为232的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 232-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 232-1]),接着在

memcache的一致性hash算法使用

一.概述 1.我们的memcache客户端(这里我看的spymemcache的源码),使用了一致性hash算法ketama进行数据存储节点的选择.与常规的hash算法思路不同,只是对我们要存储数据的key进行hash计算,分配到不同节点存储.一致性hash算法是对我们要存储数据的服务器进行hash计算,进而确认每个key的存储位置.  2.常规hash算法的应用以及其弊端 最常规的方式莫过于hash取模的方式.比如集群中可用机器适量为N,那么key值为K的的数据请求很简单的应该路由到hash(K

一致性hash算法详解

转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179 一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CARP使用的简 单哈希算法带来的问题,使得分布式哈希(DHT)可以在P2P环境中真正得到应用. 一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义: 1.平衡性(Bal

一致性Hash算法及使用场景

一.问题产生背景      在使用分布式对数据进行存储时,经常会碰到需要新增节点来满足业务快速增长的需求.然而在新增节点时,如果处理不善会导致所有的数据重新分片,这对于某些系统来说可能是灾难性的. 那么是否有可行的方法,在数据重分片时,只需要迁移与之关联的节点而不需要迁移整个数据呢?当然有,在这种情况下我们可以使用一致性Hash来处理. 二.一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot

一致性Hash算法的理解

最近在使用集团内部的TimeTunnel时,想到了中间件的订阅调度策略,可能用到一致性Hash技术,所以查阅了网上其相关的博客与资料,顺便说说自己的一些粗浅理解. 1. 应用场景     如果从十几年前的文章标题”Consistent Hashing and Random Trees“中,可以看出一致性Hash算法的最初提出,是为了解决Web服务页面访问的Cache热点问题而引入的.其实一致性Hash算法已经广泛地应用在现在的web服务中,例如: a. 外层CDN架构里的Nginx代理服务: 全