在理解了使用虚拟节点来改善一致性Hash算法的理论基础之后,就可以尝试开发代码了。编程方面需要考虑的问题是:
1、一个真实结点如何对应成为多个虚拟节点?
2、虚拟节点找到后如何还原为真实结点?
这两个问题其实有很多解决办法,我这里使用了一种简单的办法,给每个真实结点后面根据虚拟节点加上后缀再取Hash值,比如”192.168.0.0:111″就把它变成”192.168.0.0:111&&VN0″到”192.168.0.0:111&&VN4″,VN就是Virtual Node的缩写,还原的时候只需要从头截取字符串到”&&”的位置就可以了。
下面来看一下带虚拟节点的一致性Hash算法的Java代码实现:
/**
* 带虚拟节点的一致性Hash算法
* @author 五月的仓颉 http://www.cnblogs.com/xrq730/
*/
public
class
ConsistentHashingWithVirtualNode
{
/**
* 待添加入Hash环的服务器列表
*/
private
static
String[] servers = {
"192.168.0.0:111"
,
"192.168.0.1:111"
,
"192.168.0.2:111"
,
"192.168.0.3:111"
,
"192.168.0.4:111"
};
/**
* 真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用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 < servers.length; i++)
realNodes.add(servers[i]);
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 node)
{
int
hash = getHash(node);
SortedMap<Integer, String> subMap =
virtualNodes.tailMap(hash);
Integer i = subMap.firstKey();
String virtualNode = subMap.get(i);
return
virtualNode.substring(
0
, virtualNode.indexOf(
"&&"
));
}
public
static
void
main(String[] args)
{
String[] nodes = {
"127.0.0.1:1111"
,
"221.226.0.1:2222"
,
"10.211.0.1:3333"
};
for
(
int
i =
0
; i < nodes.length; i++)
System.out.println(
"["
+ nodes[i] +
"]的hash值为"
+
getHash(nodes[i]) +
", 被路由到结点["
+ getServer(nodes[i]) +
"]"
);
}
}
运行结果:
虚拟节点[
192.168
.
0.0
:
111
&&VN0]被添加, hash值为
1686427075
虚拟节点[
192.168
.
0.0
:
111
&&VN1]被添加, hash值为
354859081
虚拟节点[
192.168
.
0.0
:
111
&&VN2]被添加, hash值为
1306497370
虚拟节点[
192.168
.
0.0
:
111
&&VN3]被添加, hash值为
817889914
虚拟节点[
192.168
.
0.0
:
111
&&VN4]被添加, hash值为
396663629
虚拟节点[
192.168
.
0.1
:
111
&&VN0]被添加, hash值为
1032739288
虚拟节点[
192.168
.
0.1
:
111
&&VN1]被添加, hash值为
707592309
虚拟节点[
192.168
.
0.1
:
111
&&VN2]被添加, hash值为
302114528
虚拟节点[
192.168
.
0.1
:
111
&&VN3]被添加, hash值为
36526861
虚拟节点[
192.168
.
0.1
:
111
&&VN4]被添加, hash值为
848442551
虚拟节点[
192.168
.
0.2
:
111
&&VN0]被添加, hash值为
1452694222
虚拟节点[
192.168
.
0.2
:
111
&&VN1]被添加, hash值为
2023612840
虚拟节点[
192.168
.
0.2
:
111
&&VN2]被添加, hash值为
697907480
虚拟节点[
192.168
.
0.2
:
111
&&VN3]被添加, hash值为
790847074
虚拟节点[
192.168
.
0.2
:
111
&&VN4]被添加, hash值为
2010506136
虚拟节点[
192.168
.
0.3
:
111
&&VN0]被添加, hash值为
891084251
虚拟节点[
192.168
.
0.3
:
111
&&VN1]被添加, hash值为
1725031739
虚拟节点[
192.168
.
0.3
:
111
&&VN2]被添加, hash值为
1127720370
虚拟节点[
192.168
.
0.3
:
111
&&VN3]被添加, hash值为
676720500
虚拟节点[
192.168
.
0.3
:
111
&&VN4]被添加, hash值为
2050578780
虚拟节点[
192.168
.
0.4
:
111
&&VN0]被添加, hash值为
586921010
虚拟节点[
192.168
.
0.4
:
111
&&VN1]被添加, hash值为
184078390
虚拟节点[
192.168
.
0.4
:
111
&&VN2]被添加, hash值为
1331645117