jedis中的一致性hash算法
jredis是redis的java客户端,通过sharde实现负载路由,一直很好奇jredis的sharde如何实现,翻开jredis源码研究了一番,所谓sharde其实就是一致性hash算法。其实,通过其源码可以看出一致性hash算法实现还是比较简单的。主要实现类是redis.clients.util.Sharded<R, S>,关键的地方添加了注释:
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
|
public class Sharded<R, S extends ShardInfo<R>> { //S类封装了redis节点的信息 ,如name、权重 public static final int DEFAULT_WEIGHT = 1 ; //默认权重为1 private TreeMap<Long, S> nodes; //存放虚拟节点 private final Hashing algo; //hash算法 ...... public Sharded(List<S> shards, Hashing algo, Pattern tagPattern) { this .algo = algo; this .tagPattern = tagPattern; initialize(shards); } private void initialize(List<S> shards) { nodes = new TreeMap<Long, S>(); //基于红黑树实现排序map, 是根据key排序的 ,注意这里key放的是long类型,最多放2^32个 for ( int i = 0 ; i != shards.size(); ++i) { final S shardInfo = shards.get(i); if (shardInfo.getName() == null ) for ( int n = 0 ; n < 160 * shardInfo.getWeight(); n++) { //一个真实redis节点关联多个虚拟节点 , 通过计算虚拟节点hash值,可很好平衡把它分散到2^32个整数上 nodes.put( this .algo.hash( "SHARD-" + i + "-NODE-" + n), shardInfo); } else for ( int n = 0 ; n < 160 * shardInfo.getWeight(); n++) { //一个真实redis节点关联多个虚拟节点 , 通过计算虚拟节点hash值,可很好平衡把它分散到2^32个整数上 nodes.put( this .algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo); } resources.put(shardInfo, shardInfo.createResource()); } } /** * 计算key的hash值查找实际实际节点S * @param key * @return */ public S getShardInfo( byte [] key) { SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key)); //取出比较key的hash大的 if (tail.isEmpty()) { //取出虚拟节点为空,直接取第一个 return nodes.get(nodes.firstKey()); } return tail.get(tail.firstKey()); //取出虚拟节点第一个 } ...... } |
整个算法可总结为:首先生成一个长度为2^32个整数环,通过计算虚拟节点hash值映射到整数环上,间接也把实际节点也放到这个环上(因为虚拟节点会关联上一个实际节点)。然后根据需要缓存数据的key的hash值在整数环上查找,环顺时针找到距离这个key的hash值最近虚拟节点,这样就完成了根据key到实际节点之间的路由了。
一致性hash核心是思想是增加虚拟节点这一层来解决实际节点变动而不破坏整体的一致性。这种增加层的概念来解决问题对于我们来说一点都不陌生,如软件开发中分层设计,操作系统层解决了应用层和硬件的协调工作,java虚拟机解决了跨平台。
还有一个问题值得关注是一个实际节点虚拟多少个节点才是合适呢?认真看过上述代码同学会注意160这个值,这个实际上是经验值,太多会影响性能,太少又会影响不均衡。通过调整weight值,可实现实际节点权重,这个很好理解,虚拟出节点越多,落到这个节点概率越高。
参考资料
http://blog.csdn.net/sparkliang/article/details/5279393
http://my.oschina.net/u/90679/blog/188750
djb2 算法:
1
2
3
4
5
6
7
8
9
10
11
12
|
unsigned long hash(unsigned char *str) { //hash种子 unsigned long hash = 5381; int c; //遍历字符串中每一个字符 while (c = *str++) //对hash种子 进行位运算 hash << 5表示 hash乘以32次方,再加上 hash 表示hash乘以33 //然后再加上字符的ascii码,之后循环次操作 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } |
至于种子为什么选择 5381,通过搜索得到以下结论,该数算一个魔法常量:
5381是个奇数
5381是质数
5381是缺数
二进制分布均匀:001/010/100/000/101
由于本人对算法是一窍不通,以上特点对hash结果会有什么影响实在不懂,希望高手们能解释一下。
Redis算法对djbhash的实现方法如下(以下代码在 src/dict.c ):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//hash种子,默认为 5381 static uint32_t dict_hash_function_seed = 5381; //设置hash种子 void dictSetHashFunctionSeed(uint32_t seed) { dict_hash_function_seed = seed; } //获取hash种子 uint32_t dictGetHashFunctionSeed( void ) { return dict_hash_function_seed; } /* And a case insensitive hash function (based on djb hash) */ unsigned int dictGenCaseHashFunction( const unsigned char *buf, int len) { //得到hash种子 unsigned int hash = (unsigned int )dict_hash_function_seed; //遍历字符串 while (len--) //使用dbj算法反复乘以33并加上字符串转小写后的ascii码 hash = ((hash << 5) + hash) + ( tolower (*buf++)); /* hash * 33 + c */ return hash; } |
Redis对djbhash做了一个小小的修改,将需要处理的字符串进行了大小写的转换,是的hash算法的结果与大小写无关。
MurmurHash2算法:
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
|
uint32_t MurmurHash2( const void * key, int len, uint32_t seed ) { // 'm' and 'r' are mixing constants generated offline. // They're not really 'magic', they just happen to work well. const uint32_t m = 0x5bd1e995; const int r = 24; // Initialize the hash to a 'random' value uint32_t h = seed ^ len; // Mix 4 bytes at a time into the hash const unsigned char * data = ( const unsigned char *)key; while (len >= 4) { //每次循环都将4个字节的 字符 转成一个int类型 uint32_t k = *(uint32_t*)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; len -= 4; } // Handle the last few bytes of the input array //处理结尾不足4个字节的数据,通过移位操作将其转换为一个int型数据 switch (len) { case 3: h ^= data[2] << 16; case 2: h ^= data[1] << 8; case 1: h ^= data[0]; h *= m; }; // Do a few final mixes of the hash to ensure the last few // bytes are well-incorporated. h ^= h >> 13; h *= m; h ^= h >> 15; return h; } |
unsigned int dictGenHashFunction(const void *key, int len) {
/* 'm' and 'r' are mixing constants generated offline.
They're not really 'magic', they just happen to work well. */
uint32_t seed = dict_hash_function_seed;
const uint32_t m = 0x5bd1e995;
const int r = 24;
/* Initialize the hash to a 'random' value */
uint32_t h = seed ^ len;
/* Mix 4 bytes at a time into the hash */
const unsigned char *data = (const unsigned char *)key;
while(len >= 4) {
uint32_t k = *(uint32_t*)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
/* Handle the last few bytes of the input array */
switch(len) {
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0]; h *= m;
};
/* Do a few final mixes of the hash to ensure the last few
* bytes are well-incorporated. */
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return (unsigned int)h;
}
|
参考资料:
http://lenky.info/archives/2012/12/2150
Redis2.8.9源码 src/dict.h src/dict.c
jedis中的一致性hash算法的更多相关文章
- Jedis中的一致性hash
Jedis中的一致性hash 本文仅供大家参考,不保证正确性,有问题请及时指出 一致性hash就不多说了,网上有很多说的很好的文章,这里说说Jedis中的Shard是如何使用一致性hash的,也为大家 ...
- 分布式缓存技术memcached学习(四)—— 一致性hash算法原理
分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...
- 【转载】一致性hash算法释义
http://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html 一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karge ...
- 一致性Hash算法及使用场景
一.问题产生背景 在使用分布式对数据进行存储时,经常会碰到需要新增节点来满足业务快速增长的需求.然而在新增节点时,如果处理不善会导致所有的数据重新分片,这对于某些系统来说可能是灾难性的. 那 ...
- 分布式缓存技术memcached学习系列(四)—— 一致性hash算法原理
分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前, ...
- [转载] 一致性hash算法释义
转载自http://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html 一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Ka ...
- 分布式缓存设计:一致性Hash算法
缓存作为数据库前的一道屏障,它的可用性与缓存命中率都会直接影响到数据库,所以除了配置主从保证高可用之外还需要设计分布式缓存来扩充缓存的容量,将数据分布在多台机器上如果有一台不可用了对整体影响也比较小. ...
- 一致性Hash算法(Consistent Hash)
分布式算法 在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括: 轮循算法(Round Robin).哈希算法(HASH).最少连接算法(Least Connection).响应速度算法(Re ...
- 理解一致性Hash算法
简介 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CAR ...
随机推荐
- 移动端H5页面自适应手机屏幕宽度
1.由于本人使用的是sublime.text,使用rem就可以达到效果. 点击菜单中的preferences下的browse packages,选择cssrem-master,添加或者编写cssrem ...
- Selenium IDE的使用
Selenium IDE 的作用 Selenium IDE 是Firefox 浏览器的一个插件, 它会记录你对Firefox的操作,并且可以回放它的操作. 在实际自动化测试中,不会用Selenium ...
- 【原创】学习CGLIB动态代理中遇到的问题
代码清单1 CGLIB动态代理 package wulj.proxy.cglibProxy; import java.lang.reflect.Method; import net.sf.cglib. ...
- 异常处理、socket基于TCP协议编程
一.异常处理 1.错误和异常 1.程序中难免出现错误,而错误分成两种 (1)语法错误(这种错误过不了Python解释器的语法检测,必须在程序执行前改正) #语法错误示范一 if #语法错误示范二 de ...
- MySQL第一天
数据库课程体系 在PHP阶段,将数据库分为三个阶段: 基础阶段(就业班第一个阶段): 6天, mysql数据库的基本操作(增删改查), 以及一些高级操作(视图, 触发器,函数,存储过程等), ...
- DOM 属性操作
1 属性节点 2 attribute操作 3 value获取值操作 4 class的操作 5 指定CSS操作 1.属性节点 //获取文本节点的值 var divEle = document.getEl ...
- Java的变量命名
Java的变量命名 1.首字母是英文字母.$和下划线,由字母.数字和下划线组成. [很常规] 2.变量的命名遵循见名知义的原则. [很重要,比如名字就用 name ,而不是用a.b.c这样的命名, ...
- corethink功能模块探索开发(一)根据已有模块推测目录结构
corethink是opencmf的一个开源版本,如果自己要进行二次开发,开发模块等,需要在官方开一个中级会员(50大洋吧),官网的一个“一键生成demo”就能给你最基础的目录结构,从而可以在此基础上 ...
- mysql数据库中表的外键约束
一.外键约束 1.什么是外键? 外键指的是其他表中的主键,当做该表的外键. 2.创建外键. 2.1 可在创建表的时候直接创建外键,如图所示: create table table_name (字段名 ...
- ajax 请求后台数据返回异常 及 提示404方法名不存在
1.正常使用 Ajax 调取后台数据时,提示方法名不存在,Ajax前端正常,方法类bean注入正常,方法注解正常.但参数解析时出现异常. @RequestMapping(value="/ge ...