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

192.168.0.0:111的哈希值:1845870087 192.168.0.1:111的哈希值:1874499238 192.168.0.2:111的哈希值:1903128389 192.168.0.3:111的哈希值:1931757540 192.168.0.4:111的哈希值:1960386691

这个就问题大了,[0,232-1]的区间之中,5个HashCode值却只分布在这么小小的一个区间,什么概念?[0,232-1]中有4294967296个数字,而我们的区间只有122516605,从概率学上讲这将导致97%待路由的服务器都被路由到"192.168.0.1"这个集群点上,简直是糟糕透了!

另外还有一个不好的地方:规定的区间是非负数,String的hashCode()方法却会产生负数(不信用"192.168.1.0:1111"试试看就知道了)。不过这个问题好解决,取绝对值就是一种解决的办法。

综上,String重写的hashCode()方法在一致性Hash算法中没有任何实用价值,得找个算法重新计算HashCode。这种重新计算Hash值的算法有很多,比如CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,其中KETAMA_HASH是默认的MemCache推荐的一致性Hash算法,用别的Hash算法也可以,比如FNV1_32_HASH算法的计算效率就会高一些。

一致性Hash算法实现版本1:不带虚拟节点

使用一致性Hash算法,尽管增强了系统的伸缩性,但是也有可能导致负载分布不均匀,解决办法就是使用虚拟节点代替真实节点,第一个代码版本,先来个简单的,不带虚拟节点。

下面来看一下不带虚拟节点的一致性Hash算法的Java代码实现:

1 /** 2 * 不带虚拟节点的一致性Hash算法 3 * @author 五月的仓颉 4 * 5 */ 6 public class ConsistentHashingWithoutVirtualNode 7 { 8 /** 9 * 待添加入Hash环的服务器列表 10 */ 11 private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111", 12 "192.168.0.3:111", "192.168.0.4:111"}; 13 14 /** 15 * key表示服务器的hash值,value表示服务器的名称 16 */ 17 private static SortedMap<Integer, String> sortedMap = 18 new TreeMap<Integer, String>(); 19 20 /** 21 * 程序初始化,将所有的服务器放入sortedMap中 22 */ 23 static 24 { 25 for (int i = 0; i < servers.length; i++) 26 { 27 int hash = getHash(servers[i]); 28 System.out.println("[" + servers[i] + "]加入集合中, 其Hash值为" + hash); 29 sortedMap.put(hash, servers[i]); 30 } 31 System.out.println(); 32 } 33 34 /** 35 * 使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别 36 */ 37 private static int getHash(String str) 38 { 39 final int p = 16777619; 40 int hash = (int)2166136261L; 41 for (int i = 0; i < str.length(); i++) 42 hash = (hash ^ str.charAt(i)) * p; 43 hash += hash << 13; 44 hash ^= hash >> 7; 45 hash += hash << 3; 46 hash ^= hash >> 17; 47 hash += hash << 5; 48 49 // 如果算出来的值为负数则取其绝对值 50 if (hash < 0) 51 hash = Math.abs(hash); 52 return hash; 53 } 54 55 /** 56 * 得到应当路由到的结点 57 */ 58 private static String getServer(String node) 59 { 60 // 得到带路由的结点的Hash值 61 int hash = getHash(node); 62 // 得到大于该Hash值的所有Map 63 SortedMap<Integer, String> subMap = 64 sortedMap.tailMap(hash); 65 // 第一个Key就是顺时针过去离node最近的那个结点 66 Integer i = subMap.firstKey(); 67 // 返回对应的服务器名称 68 return subMap.get(i); 69 } 70 71 public static void main(String[] args) 72 { 73 String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"}; 74 for (int i = 0; i < nodes.length; i++) 75 System.out.println("[" + nodes[i] + "]的hash值为" + 76 getHash(nodes[i]) + ", 被路由到结点[" + getServer(nodes[i]) + "]"); 77 } 78 }

可以运行一下看一下结果:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/51abf81c7d27253bb5927771c0381c7c.html