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
publicclassSharded<R, S extendsShardInfo<R>> { //S类封装了redis节点的信息 ,如name、权重
 
    publicstaticfinalintDEFAULT_WEIGHT = 1;//默认权重为1
    privateTreeMap<Long, S> nodes;//存放虚拟节点
    privatefinalHashing algo;//hash算法
     
    ......
 
    publicSharded(List<S> shards, Hashing algo, Pattern tagPattern) {
        this.algo = algo;
        this.tagPattern = tagPattern;
        initialize(shards);
    }
 
    privatevoidinitialize(List<S> shards) {
        nodes = newTreeMap<Long, S>();//基于红黑树实现排序map, 是根据key排序的 ,注意这里key放的是long类型,最多放2^32个
 
        for(inti = 0; i != shards.size(); ++i) {
            finalS shardInfo = shards.get(i);
            if(shardInfo.getName() == null)
                for(intn = 0; n < 160* shardInfo.getWeight(); n++) {
                    //一个真实redis节点关联多个虚拟节点  , 通过计算虚拟节点hash值,可很好平衡把它分散到2^32个整数上
                    nodes.put(this.algo.hash("SHARD-"+ i + "-NODE-"+ n), shardInfo);
                }
            else
                for(intn = 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
     */
    publicS getShardInfo(byte[] key) {
        SortedMap<Long, S> tail = nodes.tailMap(algo.hash(key));//取出比较key的hash大的
        if(tail.isEmpty()) {//取出虚拟节点为空,直接取第一个
            returnnodes.get(nodes.firstKey());
        }
        returntail.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

【Redis Dict 中的MurmurHash2算法算法】【http://my.oschina.net/fuckphp/blog/270258】
        Redis 中很多地方用到了hash算法,比如在向 key space中插入新的key的时候,或者在实现hashset数据结构的时候都用到了hash算法,今天主要记录一下dict中用到的两种hash算法:djb2 hash function 和 MurmurHash2两种算法。

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, intlen) {
    //得到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

Redis 设计与实现(第一版)

djb hash function

http://code.google.com/p/smhasher/

jedis中的一致性hash算法的更多相关文章

  1. Jedis中的一致性hash

    Jedis中的一致性hash 本文仅供大家参考,不保证正确性,有问题请及时指出 一致性hash就不多说了,网上有很多说的很好的文章,这里说说Jedis中的Shard是如何使用一致性hash的,也为大家 ...

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

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

  3. 【转载】一致性hash算法释义

    http://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html 一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Karge ...

  4. 一致性Hash算法及使用场景

    一.问题产生背景      在使用分布式对数据进行存储时,经常会碰到需要新增节点来满足业务快速增长的需求.然而在新增节点时,如果处理不善会导致所有的数据重新分片,这对于某些系统来说可能是灾难性的. 那 ...

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

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

  6. [转载] 一致性hash算法释义

    转载自http://www.cnblogs.com/haippy/archive/2011/12/10/2282943.html 一致性Hash算法背景 一致性哈希算法在1997年由麻省理工学院的Ka ...

  7. 分布式缓存设计:一致性Hash算法

    缓存作为数据库前的一道屏障,它的可用性与缓存命中率都会直接影响到数据库,所以除了配置主从保证高可用之外还需要设计分布式缓存来扩充缓存的容量,将数据分布在多台机器上如果有一台不可用了对整体影响也比较小. ...

  8. 一致性Hash算法(Consistent Hash)

    分布式算法 在做服务器负载均衡时候可供选择的负载均衡的算法有很多,包括: 轮循算法(Round Robin).哈希算法(HASH).最少连接算法(Least Connection).响应速度算法(Re ...

  9. 理解一致性Hash算法

    简介 一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题,初衷和CARP十分类似.一致性哈希修正了CAR ...

随机推荐

  1. jquery lazyload延迟加载技术的实现原理分析_jquery

    前言 懒加载技术(简称lazyload)并不是新技术,它是js程序员对网页性能优化的一种方案.lazyload的核心是按需加载.在大型网站中都有lazyload的身影,例如谷歌的图片搜索页,迅雷首页, ...

  2. 解决john不能开多个进程的问题

    在使用john进行shadow文件破解时,如果已经开了一个john的进程,这回提示以下错误: Crash recovery file is locked: /root/.john/john.rec   ...

  3. centos6.9下安装composer

    1.下载composer curl -sS https://getcomposer.org/installer | php ps:如果出现php无法运行的情况,请先把PHP加入环境变量 2.移动或复制 ...

  4. linux 文件和文件夹的ctime,mtime,atime的差别

    多了不再赘述,看以下解释 st_atime            Time when file data was last accessed. Changed by  the            f ...

  5. Eclipse中svn操作

    1.主干和分支间合并代码 合并根据目标不同分为2种: 1.分支合并到主干:主要用在修复完生产BUG,并上线之后.需把改动的代码合并到主干上. 2.主干合并到分支:公用的逻辑改动,需反映到所有并行的分支 ...

  6. C#多线程基础,适合新手了解

    一.创建线程 在整个系列文章中,我们主要使用Visual Studio 2015作为线程编程的主要工具.在C#语言中创建.使用线程只需要按以下步骤编写即可: 1.启动Visual Studio 201 ...

  7. LeetCode:螺旋矩阵||【59】

    LeetCode:螺旋矩阵||[59] 题目描述 给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵. 示例: 输入: 3 输出: [ [ 1, 2, 3 ...

  8. ssh登陆virtualbox虚拟机

  9. OpenGL学习进程(9)在3D空间的绘制实例

        本节将演示在3D空间中绘制图形的几个简单实例:     (1)在3D空间内绘制圆锥体: #include <GL/glut.h> #include <math.h> # ...

  10. 每天一个Linux命令(63)scp命令

        scp(secure copy)用于进行远程文件拷贝.     (1)用法:     用法:  scp [参数] [源文件] [目标文件]     (2)功能:     功能:  scp在主机 ...