转自:http://colobu.com/2015/04/13/consistent-hash-algorithm-in-java-memcached-client/

memcached Java客户端spymemcached的一致性Hash算法

最近看到两篇文章,一个是江南白衣的陌生但默默一统江湖的MurmurHash,另外一篇是张洋的一致性哈希算法及其在分布式系统中的应用。虽然我在项目中使用memcached的java客户端spymemcached好几年了,但是对它的一致性哈希算法的细节从来没有仔细研究过。趁此机会,特别的看了一下它的源代码。

我们知道,Memcached本身没有提供分布式的功能,一般客户端会实现一致性Hash算法,根据Key值计算出应该在哪个节点进行存取。

Ketama Hash的实现

spymemcached实现了几种Hash算法:NATIVE_HASH,CRC_HASH,FNV1_64_HASH,FNV1A_64_HASH,FNV1_32_HASH,FNV1A_32_HASH,KETAMA_HASH。
相比较前几个hash算法,KETAMA HASH算法可以将服务器的虚拟节点相对均匀的分布到环上,它是一种基于MD5散列的Hash算法。
下面这个类是我精简的spymemcached的KetamaNodeLocator类,用来测试生成的虚拟节点的分布情况,它会打印出两个虚拟节点之间的间隔。 如果间隔比较均匀,我们相信使用同样的Hash算法计算的key值应该可以均匀的落在每个节点上。

spymemcached为每个节点计算虚拟节点时使用节点地址 + "-i"格式, i最大的每个节点的虚拟节点数,默认是160个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.colobu.consistenthashing;
 
import java.util.List;
import java.util.TreeMap;
 
public class Ketama {
public TreeMap<Long, Node> hashNodes;
public HashAlgorithm hashAlgorithm;
 
protected void setKetamaNodes(List<Node> nodes) {
TreeMap<Long, Node> newNodeMap = new TreeMap<Long, Node>();
int numReps = 160;
for (Node node : nodes) {
if (hashAlgorithm == HashAlgorithm.KETAMA_HASH) {
for (int i = 0; i < numReps / 4; i++) {
byte[] digest = HashAlgorithm.computeMd5(node.getName() + "-" + i);
for (int h = 0; h < 4; h++) {
Long k = ((long) (digest[3 + h * 4] & 0xFF) << 24)
| ((long) (digest[2 + h * 4] & 0xFF) << 16)
| ((long) (digest[1 + h * 4] & 0xFF) << 8)
| (digest[h * 4] & 0xFF);
newNodeMap.put(k, node);
 
}
}
} else {
for (int i = 0; i < numReps; i++) {
newNodeMap.put(hashAlgorithm.hash(node + "-" + i), node);
}
}
}
 
hashNodes = newNodeMap;
}
}

写一个测试类,看看虚拟节点的分布情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.colobu.consistenthashing;
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
 
public class Main {
 
public static void main(String[] args) {
 
//System.out.println("测试 ketama hash");
//testKetama();
//System.out.println("\r\n\r\n测试 native hash");
//testHash(HashAlgorithm.NATIVE_HASH);
//System.out.println("\r\n\r\n测试 CRC hash"); //max=32767
//testHash(HashAlgorithm.CRC_HASH);
//System.out.println("\r\n\r\n测试 FNV1_64_HASH");
//testHash(HashAlgorithm.FNV1_64_HASH);
//System.out.println("\r\n\r\n测试 FNV1A_64_HASH");
//testHash(HashAlgorithm.FNV1A_64_HASH);
 
//System.out.println("\r\n\r\n测试 MurmurHash 32");
//testHash(HashAlgorithm.MurmurHash_32);
 
System.out.println("\r\n\r\n测试 MurmurHash 64");
testHash(HashAlgorithm.MurmurHash_64);
}
 
private static void testHash(HashAlgorithm hash) {
Ketama ketama = new Ketama();
ketama.hashAlgorithm = hash;
 
List<Node> nodes = new ArrayList<>();
for(int i=0; i< 10; i++) {
nodes.add(new Node("name-" + i));
}
 
ketama.setKetamaNodes(nodes);
Iterator<Entry<Long, Node>> it = ketama.hashNodes.entrySet().iterator();
Entry<Long, Node> prior = it.next();
while(it.hasNext()) {
Entry<Long, Node> current = it.next();
System.out.println("间隔:" + (current.getKey() - prior.getKey()) + "=" + current.getKey() + "-" + prior.getKey());
prior = current;
}
 
}
 
private static void testKetama() {
Ketama ketama = new Ketama();
ketama.hashAlgorithm = HashAlgorithm.KETAMA_HASH;
 
List<Node> nodes = new ArrayList<>();
for(int i=0; i< 10; i++) {
nodes.add(new Node("name-" + i));
}
 
ketama.setKetamaNodes(nodes);
Iterator<Entry<Long, Node>> it = ketama.hashNodes.entrySet().iterator();
Entry<Long, Node> prior = it.next();
while(it.hasNext()) {
Entry<Long, Node> current = it.next();
System.out.println("间隔:" + (current.getKey() - prior.getKey()));
prior = current;
}
}
 
}

实际结果看到ketama算法还是不错的。

加入MurmurHash算法

江南白衣的那篇文章介绍了MurmurHash算法,开源中国社区也翻译了一篇 Hash 函数概览的科普文章。
如果我们将MurmurHash算法加入到spymemcached会怎么样呢。我没有测试它的性能,但是从分布上来看还是不错的。
网上有几个MurmurHash的实现,如GuavaCassandra等。我不想额外引入第三方的包,所以直接复制了Viliam Holub的实现

在HashAlgorithm算法中加入MurmurHash枚举类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package com.colobu.consistenthashing;
 
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.CRC32;
 
public enum HashAlgorithm {
 
/**
* Native hash (String.hashCode()).
*/
NATIVE_HASH,
/**
* CRC_HASH as used by the perl API. This will be more consistent both
* across multiple API users as well as java versions, but is mostly likely
* significantly slower.
*/
CRC_HASH,
/**
* FNV hashes are designed to be fast while maintaining a low collision rate.
* The FNV speed allows one to quickly hash lots of data while maintaining a
* reasonable collision rate.
*
* @see <a href="http://www.isthe.com/chongo/tech/comp/fnv/">fnv
* comparisons</a>
* @see <a href="http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash">fnv at
* wikipedia</a>
*/
FNV1_64_HASH,
/**
* Variation of FNV.
*/
FNV1A_64_HASH,
/**
* 32-bit FNV1.
*/
FNV1_32_HASH,
/**
* 32-bit FNV1a.
*/
FNV1A_32_HASH,
MurmurHash_32,
MurmurHash_64,
/**
* MD5-based hash algorithm used by ketama.
*/
KETAMA_HASH;
 
private static final long FNV_64_INIT = 0xcbf29ce484222325L;
private static final long FNV_64_PRIME = 0x100000001b3L;
 
private static final long FNV_32_INIT = 2166136261L;
private static final long FNV_32_PRIME = 16777619;
 
private static MessageDigest md5Digest = null;
 
static {
try {
md5Digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not supported", e);
}
}
 
/**
* Compute the hash for the given key.
*
* @return a positive integer hash
*/
public long hash(final String k) {
long rv = 0;
int len = k.length();
switch (this) {
case NATIVE_HASH:
rv = k.hashCode();
break;
case CRC_HASH:
// return (crc32(shift) >> 16) & 0x7fff;
CRC32 crc32 = new CRC32();
crc32.update(k.getBytes());
rv = (crc32.getValue() >> 16) & 0x7fff;
break;
case FNV1_64_HASH:
// Thanks to pierre@demartines.com for the pointer
rv = FNV_64_INIT;
for (int i = 0; i < len; i++) {
rv *= FNV_64_PRIME;
rv ^= k.charAt(i);
}
break;
case FNV1A_64_HASH:
rv = FNV_64_INIT;
for (int i = 0; i < len; i++) {
rv ^= k.charAt(i);
rv *= FNV_64_PRIME;
}
break;
case FNV1_32_HASH:
rv = FNV_32_INIT;
for (int i = 0; i < len; i++) {
rv *= FNV_32_PRIME;
rv ^= k.charAt(i);
}
break;
case FNV1A_32_HASH:
rv = FNV_32_INIT;
for (int i = 0; i < len; i++) {
rv ^= k.charAt(i);
rv *= FNV_32_PRIME;
}
break;
case MurmurHash_32:
rv = MurmurHash.hash32(k);
break;
case MurmurHash_64:
rv = MurmurHash.hash64(k);
break;
case KETAMA_HASH:
byte[] bKey = computeMd5(k);
rv = ((long) (bKey[3] & 0xFF) << 24)
| ((long) (bKey[2] & 0xFF) << 16)
| ((long) (bKey[1] & 0xFF) << 8)
| (bKey[0] & 0xFF);
break;
default:
assert false;
}
return rv & 0xffffffffL; /* Truncate to 32-bits */
}
 
/**
* Get the md5 of the given key.
*/
public static byte[] computeMd5(String k) {
MessageDigest md5;
try {
md5 = (MessageDigest) md5Digest.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("clone of MD5 not supported", e);
}
md5.update(k.getBytes());
return md5.digest();
}
}

实际结果看MurmurHash也是相当的均匀。

xmemcached的实现

xmemcached是另外一个memcached java客户端,它实现了类似spymemcached的hash算法。只不过增加了几种新的hash算法:MYSQL_HASH,ELF_HASH,RS_HASH,LUA_HASH,ONE_AT_A_TIME。

Twemproxy

Twemproxy是一个Memcahced的网关程序。 它实现了下面几种Hash算法。

  • one_at_a_time
  • md5
  • crc16
  • crc32 (crc32 implementation compatible with libmemcached)
  • crc32a (correct crc32 implementation as per the spec)
  • fnv1_64
  • fnv1a_64
  • fnv1_32
  • fnv1a_32
  • hsieh
  • murmur
  • jenkins

转: memcached Java客户端spymemcached的一致性Hash算法的更多相关文章

  1. 分布式缓存技术memcached学习(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...

  2. 分布式缓存技术memcached学习系列(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前, ...

  3. Java实现一致性Hash算法

    Java代码实现了一致性Hash算法,并加入虚拟节点.,具体代码为: package com.baijob.commonTools;   import java.util.Collection; im ...

  4. 分布式一致性hash算法

    写在前面  在学习Redis的集群内容时,看到这么一句话:Redis并没有使用一致性hash算法,而是引入哈希槽的概念.而分布式缓存Memcached则是使用分布式一致性hash算法来实现分布式存储. ...

  5. 一致性Hash算法在Memcached中的应用

    前言 大家应该都知道Memcached要想实现分布式只能在客户端来完成,目前比较流行的是通过一致性hash算法来实现.常规的方法是将server的hash值与server的总台数进行求余,即hash% ...

  6. (转) 一致性Hash算法在Memcached中的应用

    前言 大家应该都知道Memcached要想实现分布式只能在客户端来完成,目前比较流行的是通过一致性hash算法来实现.常规的方法是将 server的hash值与server的总台数进行求余,即hash ...

  7. memcached和一致性hash算法

    1 一致性hash算法的一致性 这里的一致性指的是该算法可以保持memcached和数据库中的数据的一致性. 2 什么是一致性hash算法 2.1 为什么需要一致性hash算法 现在有大量的key v ...

  8. 一致性hash算法在memcached中的使用

    一.概述 1.我们的memcacheclient(这里我看的spymemcache的源代码).使用了一致性hash算法ketama进行数据存储节点的选择.与常规的hash算法思路不同.仅仅是对我们要存 ...

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

    一致性Hash算法 关于一致性Hash算法,在我之前的博文中已经有多次提到了,MemCache超详细解读一文中"一致性Hash算法"部分,对于为什么要使用一致性Hash算法.一致性 ...

随机推荐

  1. perl中foreach(二)

    本文和大家重点讨论一下Perl foreach命令的用法,Perl foreach循环中控制变量的值会被Perl自动保存和恢复.当循环进行时,是没有办法改变其值的.循环结束时,变量的值会回到循环开始前 ...

  2. windows终端输入pip install requests报错:Fatal error in launcher

    emm今天群友发了个图,说他的pip报错,是这个问题 emmm这个问题我也不太懂,后来让他pip install requests这样操作,, 还是不管用,我寻思这个错咋回事,让他用  python  ...

  3. python的部分内置函数

    内置函数思维导图:https://www.processon.com/mindmap/5c10ca52e4b0c2ee256ac034 内置函数 匿名函数 匿名函数统一的名字是:<lambda& ...

  4. web开发框架之Django基础

    在脚本中如何进行Django的运行 if __name__ == '__main__': import os import django # 注意路径(当前所在的位置,要加载Django的配置文件) ...

  5. LeetCode101--对称二叉树

    ''' 给定一个二叉树,检查它是否是镜像对称的. ''' class TreeNode: def __init__(self, x): self.val = x self.left = None se ...

  6. x86保护模式 控制寄存器和系统地址寄存器

    控制寄存器和系统地址寄存器 控制寄存器    crx cr0   指示cpu工作方式的控制位  包含启用和禁止分页管理机制的控制位  包含控制浮点协处理器操作的控制位   注意必须为0的位 cr2和c ...

  7. [git 学习篇] git remote add origin错误

    http://blog.csdn.net/dengjianqiang2011/article/details/9260435 如果输入$ Git remote add origin git@githu ...

  8. 九度oj 题目1385:重建二叉树

    题目描述: 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7 ...

  9. 九度oj 题目1472:求两个多项式的和

    题目描述: 输入两个多项式,计算它们的和. 每个多项式有若干对整数表示,每组整数中,第一个整数表示系数(非0),第二个整数表示该项的次数. 如由3 3 5 -2 1 4 0表示3x^5 - 2 * x ...

  10. SPOJ GSS5 Can you answer these queries V ——线段树

    [题目分析] GSS1上增加区间左右端点的限制. 直接分类讨论就好了. [代码] #include <cstdio> #include <cstring> #include & ...