题记------学习别人的精髓,并加以总结,消化吸收,这就是提高!!!
在拜读前阿里巴巴技术大牛李智慧先生的著作《大型网站技术架构:核心原理与案例分析》时,第一次比较完备的了解了一致性hash算法, 一致性哈希算法早在1997年由麻省理工学院提出的一种分布式哈希(DHT)实现算法,而该算法的核心是将hash环的数据结构实现KEY到缓存服务器的HASH映射。一致性hash算法的大力推广实质是由于随着以淘宝等大型网站的兴起,对服务器提出了更高的要求,而最初始的服务器数量,显然无法满足大型网站数以亿计的访问量,服务器扩容已经刻不容缓。然而悲剧发生了,当购置一台新服务器并投入使用,该服务器如一张白纸,该服务器上没有任何缓存,严重违背了网站架构中负载均衡的原则,同时对于娇气的数据库服务器由于习惯了缓存所带来的安逸生活,陡然负载增加,不堪重负,这大大提高了数据库服务器宕机的风险,此时简单的路由算法显然要被淘汰出局,一致性hash算法被提上议程... ...
评判一个hash算法的好坏,最主要看两个指标平衡性、单调性
1、平衡性:哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。
2、单调性:如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。
对于一致性hash算法的概念,理解有一篇博文写的非常通俗易懂(http://www.zsythink.net/archives/1182),有兴趣的可以去看看,我主要用java模拟下一致性hash算法的实现
1 package com.gongli.suanfa; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.Map.Entry; 6 import java.util.Set; 7 import java.util.SortedMap; 8 import java.util.TreeMap; 9 10 import org.junit.Test; 11 12 /** 13 * 14 * @author gongli 15 * 一致性hash算法,未添加虚拟节点 16 */ 17 public class ConsistentHash { 18 /** 19 * 生成待添加到Hash环的服务器列表 20 */ 21 public List<StringBuffer> createServer(){ 22 List<StringBuffer> list = new ArrayList<StringBuffer>(); 23 for(int i=0;i<(int)(Math.random()*100)+1;i++){ 24 StringBuffer sb = new StringBuffer("192.168.");//假设服务器IP地址以192.168开头 25 sb.append((int)(Math.random()*1000)+"."+((int)(Math.random()*100)+1)+":" 26 +((int)(Math.random()*1000)+100)); 27 list.add(sb); 28 } 29 return list; 30 } 31 32 /** 33 * 使用FNVl_32_HASH计算服务器的HASH值 34 * 此处不使用native_hash算法,该算法也就是java默认的hash算法,不使用该算法有两点原因 35 * 1、native_hash算法可能产生负数,比如192.168.1.1:1111的hash值为-677657723 36 * 2、native_hash算法产生的节点过于集中,分布极不均匀,严重违反了平衡性原则 37 * 经过查阅资料FNVl_32_HASH效率略低于native_hash但比KETAMA_HASH效率高 38 */ 39 public int hashCode(StringBuffer str) { 40 final int p = 16777619; 41 int hash = (int)2166136261L; 42 for (int i = 0; i < str.length(); i++){ 43 hash = (hash ^ str.charAt(i)) * p; 44 hash += hash << 13; 45 hash ^= hash >> 7; 46 hash += hash << 3; 47 hash ^= hash >> 17; 48 hash += hash << 5; 49 //对负值作绝对值,那么有可能会和正常hash出来的正值出现碰撞,但概率非常低可以不考虑 50 if (hash < 0){ 51 hash = Math.abs(hash); 52 } 53 } 54 return hash; 55 } 56 57 /** 58 * 服务器应映射到HASH环上的节点,第一种算法 59 */ 60 // public SortedMap<Integer,String> getServerNode(StringBuffer node){ 61 // int serverHash = hashCode(node);//得到服务器的HASH值 62 // //key表示服务器的hash值,value表示服务器的名称 63 // SortedMap<Integer,String> map = new TreeMap<Integer,String>(); 64 // map.put(serverHash, node.toString()); 65 // //得到大于该map键值的所有map 66 // SortedMap<Integer,String> bigerAllMap = map.tailMap(serverHash); 67 // //得到大于该map键值的所有map中的最小值,也就是key顺时针过去离node最近的那个结点的键值 68 // Integer key = bigerAllMap.firstKey(); 69 // //返回目的节点 70 // SortedMap<Integer,String> goalMap = new TreeMap<Integer,String>(); 71 // goalMap.put(key, bigerAllMap.get(key)); 72 // return goalMap; 73 // } 74 75 /** 76 * 服务器应映射到HASH环上的节点,第二种算法 77 */ 78 public SortedMap<Integer,String> getServerNode(StringBuffer node){ 79 int serverHash = hashCode(node);//得到服务器的HASH值 80 //key表示服务器的hash值,value表示服务器的名称 81 TreeMap<Integer,String> map = new TreeMap<Integer,String>(); 82 map.put(serverHash, node.toString()); 83 //返回严格大于给定键的最小键;如果不存在这样的键,则返回 null 84 Integer key = map.higherKey(serverHash); 85 if(key==null){ 86 key = map.firstKey();//如果没有严格大于给定键的最小键,则返回第一个键 87 } 88 //返回目的节点 89 SortedMap<Integer,String> goalMap = new TreeMap<Integer,String>(); 90 goalMap.put(key, map.get(key)); 91 return goalMap; 92 } 93 94 /** 95 * 测试未加虚拟节点时的一致性HASH算法 96 */ 97 @Test 98 public void test(){ 99 List<StringBuffer> list = createServer(); 100 for(StringBuffer sb : list){ 101 SortedMap<Integer,String> node = getServerNode(sb); 102 Set<Entry<Integer,String>> set = node.entrySet(); 103 for(Entry<Integer,String> n : set){ 104 System.out.println("hash值为"+n.getKey()+" "+"路由到节点"+n.getValue()); 105 } 106 } 107 } 108 }
带虚拟节点的一致性hash算法和不带虚拟节点的差不多,只是在每个实际节点的后面顺时针加若干个虚拟节点就行了,每个节点值类似于192,168.1.1#1,键为值取hash值即可