今天重新看了一致性hash算法,做了一下笔记,先保存下来吧,第一次手动实现了一遍,虽然有一部分是参考百度上的内容,但是也是根据bug自己改了一下
以下是网上参考以及自己理解的笔记:
以下代码参考网络:
package zlpTest;
import java.util.SortedMap;
import java.util.TreeMap;
//不带虚拟节点的一致性hash算法
public class ConsistentHashingWithouVirtualNode {
//待加入hash环的服务器列表
private static String Server[]={"192.168.0.112:1000","192.168.0.112:1001","192.168.0.112:1002","192.168.0.112:1003","192.168.0.112:1004"};
//key表示服务器的hash值,value表示服务器
private static SortedMap<Integer, String>sortedMap=new TreeMap<Integer, String>();
//程序初始化,将服务器加到sortmap中
static {
for(int i =0;i<Server.length;i++){
int hash = getHash(Server[i]);
sortedMap.put(hash, Server[i]);
System.out.println(Server[i]+"加入到集合中,其hash值为:"+hash);
}
}
//得到应当路由到的节点
private static String getServer(String key){
//得到key的hash值
int hash = getHash(key);
//得到大于该hash值得所有的map
SortedMap<Integer, String> subMap = sortedMap.tailMap(hash);
if(subMap.isEmpty()){
//如果没有比该key的hash值大的,则从第一个node开始
Integer i = sortedMap.firstKey();
//返回对应的服务器
return sortedMap.get(i);
}else{
//第一个key就是顺时针过去离node最近的那个节点
Integer i = subMap.firstKey();
//返回对应的服务器
return subMap.get(i);
}
}
//计算服务器的hash值
private static int getHash(String str) {
final int p = 16777619;
int hash=(int) 2166136261L;
for(int i =0;i<str.length();i++){
hash = (hash^str.charAt(i))*p;
hash += hash <<13;
hash ^= hash >>7;
hash ^= hash >>17;
hash += hash <<5;
}
//如果算出来的值为负数则取其绝对值
if(hash<0){
hash = Math.abs(hash);
}
return hash;
}
public static void main(String args[]){
String[] keys = {"太阳","月亮","星星"};
for(int i = 0; i <keys.length;i++){
System.out.println("["+keys[i]+"]的hash值为"+getHash(keys[i])+",被路由到节点["+getServer(keys [i])+"]");
}
}
}
结果如下:
192.168.0.112:1000加入到集合中,其hash值为:481136787
192.168.0.112:1001加入到集合中,其hash值为:1207862292
192.168.0.112:1002加入到集合中,其hash值为:970668381
192.168.0.112:1003加入到集合中,其hash值为:255327084
192.168.0.112:1004加入到集合中,其hash值为:2131030089
[太阳]的hash值为606807096,被路由到节点[192.168.0.112:1002]
[月亮]的hash值为244383856,被路由到节点[192.168.0.112:1003]
[星星]的hash值为1233020469,被路由到节点[192.168.0.112:1004]
package zlpTest;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
以下代码实现中,网络参考上有bug,已经修改,可以正常运行
public class ConsistentHashingWithVirtualNode {
//待加入hash环的服务器列表
private static String Server[]={"192.168.0.112:1000","192.168.0.112:1001","192.168.0.112:1002","192.168.0.112:1003","192.168.0.112:1004"};
//真实节点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好
private static List<String> realNodes = new LinkedList<String>();
//虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称
private static SortedMap<Integer, String> virtualNodes = new TreeMap<Integer, String>();
//虚拟节点的数目,这里写死,一个真实节点对应5个虚拟节点
private static final int VIRTUAL_NODES = 5;
static{
//先把原始的服务器添加到真实节点列表中
for(int i = 0;i<Server.length;i++){
realNodes.add(Server[i]);
}
//再添加虚拟节点,遍历linkedList使用foreach循环效率会比较高
for(String str : realNodes){
for(int i =0;i<VIRTUAL_NODES;i++){
String virtualNodeName = str+"&&VN"+String.valueOf(i);
int hash = getHash(virtualNodeName);
System.out.println("虚拟节点["+virtualNodeName+"]被添加,hash值为"+hash);
virtualNodes.put(hash, virtualNodeName);
}
}
System.out.println();
}
//使用FNV1_32_HASH算法计算服务器的Hash值,这里不使用重写hashCode的方法,最终效果没区别
private static int getHash(String str ) {
final int p = 16777619;
int hash = (int)2166136261L;
for (int i = 0; i < str.length(); i++){
hash = (hash ^ str.charAt(i)) * p;
}
hash += hash << 13;
hash ^= hash >> 7;
hash += hash << 3;
hash ^= hash >> 17;
hash += hash << 5;
// 如果算出来的值为负数则取其绝对值
if (hash < 0) {
hash = Math.abs(hash);
}
return hash;
}
//得到应当路由到的节点
private static String getServer(String key){
//得到该key的hash值
int hash = getHash(key);
//得到大于该hash值得所有map
SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
String virtualNode;
if(subMap.isEmpty()){
//如果没有比该key的hash值大的,则从第一个node开始
Integer i = virtualNodes.firstKey();
//返回对应的服务器
virtualNode = subMap.get(i);
}else{
//第一个key就是顺时针过去离node最近的那个节点
Integer i = subMap.firstKey();
//返回对应的服务器
virtualNode = subMap.get(i);
}
//virtualNode虚拟节点名称要截取一下
if(virtualNode.equals(null)){//可以修改为这种
return virtualNode.substring(0,virtualNode.indexOf("&&"));
}
return virtualNode;// 这个地方之前返回的都是null,现在可以正常运行
}
public static void main(String[] args){
String[] keys = {"太阳", "月亮", "星星"};
for(int i=0; i<keys.length; i++)
System.out.println("[" + keys[i] + "]的hash值为" +
getHash(keys[i]) + ", 被路由到结点[" + getServer(keys[i]) + "]");
}
}
运行结果:
虚拟节点[192.168.0.112:1000&&VN0]被添加,hash值为426605410
虚拟节点[192.168.0.112:1000&&VN1]被添加,hash值为806679074
虚拟节点[192.168.0.112:1000&&VN2]被添加,hash值为1854117913
虚拟节点[192.168.0.112:1000&&VN3]被添加,hash值为1685468505
虚拟节点[192.168.0.112:1000&&VN4]被添加,hash值为819484125
虚拟节点[192.168.0.112:1001&&VN0]被添加,hash值为1631497057
虚拟节点[192.168.0.112:1001&&VN1]被添加,hash值为919025337
虚拟节点[192.168.0.112:1001&&VN2]被添加,hash值为1323825146
虚拟节点[192.168.0.112:1001&&VN3]被添加,hash值为280852816
虚拟节点[192.168.0.112:1001&&VN4]被添加,hash值为648131592
虚拟节点[192.168.0.112:1002&&VN0]被添加,hash值为857453929
虚拟节点[192.168.0.112:1002&&VN1]被添加,hash值为239883060
虚拟节点[192.168.0.112:1002&&VN2]被添加,hash值为887503354
虚拟节点[192.168.0.112:1002&&VN3]被添加,hash值为548174262
虚拟节点[192.168.0.112:1002&&VN4]被添加,hash值为392490292
虚拟节点[192.168.0.112:1003&&VN0]被添加,hash值为96663676
虚拟节点[192.168.0.112:1003&&VN1]被添加,hash值为1227495797
虚拟节点[192.168.0.112:1003&&VN2]被添加,hash值为559011748
虚拟节点[192.168.0.112:1003&&VN3]被添加,hash值为296302209
虚拟节点[192.168.0.112:1003&&VN4]被添加,hash值为1211728920
虚拟节点[192.168.0.112:1004&&VN0]被添加,hash值为1881924403
虚拟节点[192.168.0.112:1004&&VN1]被添加,hash值为1991114675
虚拟节点[192.168.0.112:1004&&VN2]被添加,hash值为1652311339
虚拟节点[192.168.0.112:1004&&VN3]被添加,hash值为1096234241
虚拟节点[192.168.0.112:1004&&VN4]被添加,hash值为1405901184
[太阳]的hash值为1977106057, 被路由到结点[192.168.0.112:1004&&VN1]
[月亮]的hash值为1132637661, 被路由到结点[192.168.0.112:1003&&VN4]
[星星]的hash值为880019273, 被路由到结点[192.168.0.112:1002&&VN2]
参考网址:
http://blog.csdn.net/cywosp/article/details/23397179
http://blog.csdn.net/u010558660/article/details/52767218