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. 0x05 MySQL 数据操作

    一 插入数据INSERT 1. 插入完整数据(顺序插入) 语法一: INSERT INTO 表名(字段1,字段2,字段3…字段n) VALUES(值1,值2,值3…值n); 语法二: INSERT I ...

  2. 005-快捷键,host,查看版本

    一.系统快捷键 设置 键盘--查看 shift+ctrl +print  区域截图至剪切版 ctrl+alt+箭头     切换工作区 自定义打开终端快捷键 设置->键盘->自定义:名称: ...

  3. 001-centos7安装 笔记本 联想G510

    一.准备前提 1.联想G510AT 用winpe进入笔记本电脑,找到一个分区,删除即可 2.使用U盘安装 2.1.准备一个8G 的U盘,格式化ntfs. 2.2.在window下,下载UltraISO ...

  4. Tips for Unix/Linux

    @1: 在单个命令中创建目录树:不要逐层创建目录,尽量使用mkdir的-p选项: ~$ mkdir -p one/two/three # 假设目录one不存在 创建复杂的目录树: ~$ mkdir - ...

  5. dockfile

    dockerfile是对镜像的描述 新建一个dockfile文件 docker inspect

  6. val() attr('value')

    val() 只能更改输入框内的值,能更改value属性, 在浏览器中体现不出value被改变 attr('value') 都可以 谷歌浏览器 val,attr都能获取输入框最新的value值

  7. The Maximum Unreachable Node Set 【17南宁区域赛】 【二分匹配】

    题目链接 https://nanti.jisuanke.com/t/19979 题意 给出n个点 m 条边 求选出最大的点数使得这个点集之间 任意两点不可达 题目中给的边是有向边 思路 这道题 实际上 ...

  8. nginx安装使用

    1.前言 当我们希望分享自己的文件时,有多种方式,局域网可以采用共享,rtx传输,qq传输,发送到邮箱,直接u盘拷贝等等.但最简单的就是开启本地服务器,其他电脑通过网页的方式直接下载,这里介绍使用ng ...

  9. 数字代币ICO

    随着比特币.莱特币.以太币的逐步兴起,越来越多的数字代币开始衍生,虚拟货币扑朔迷离,一不小心就被人割了韭菜..... 从荷兰IPO的故事说起 400多年前,西方有一群精英海盗开了一家公司.为了顺利拓展 ...

  10. require.js和sea.js的区别

    下面为大家讲解一下require.js和sea.js的区别.纯属个人意见,不喜勿喷. 首先原理上的区别 sea.js遵循CMD规范.书写方式类似node.js的书写模板代码.依赖的自动加载,配置的简洁 ...