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. the core of Git is a simple key-value data store The objects directory stores all the content for your database

    w https://git-scm.com/book/en/v1/Git-Internals-Plumbing-and-Porcelain Git is a content-addressable f ...

  2. C#反射Assembly 详细说明(转)

    1.对C#反射机制的理解2.概念理解后,必须找到方法去完成,给出管理的主要语法3.最终给出实用的例子,反射出来dll中的方法 反射是一个程序集发现及运行的过程,通过反射可以得到*.exe或*.dll等 ...

  3. ES6学习笔记(三)——数值的扩展

    看到这条条目录有没有感觉很枯燥,觉得自己的工作中还用不到它所以实在没有耐心看下去,我也是最近得闲,逼自己静下心来去学习去总结,只有在别人浮躁的时候你能静下心来去学去看去总结,你才能进步.毕竟作为前端不 ...

  4. MySQL二进制包安装简略过程

    l  软件目录 [root@MASTER_03 ~]# mkdir -pv /data/software [root@MASTER_03 ~]# cd /data/software/ [root@MA ...

  5. 重写toString方法

    当你要读取关于对象的一些有用细节时,可以在对象上调用toString(). 如,当把一个对象引用传递给System.out.println();时,该对象的toString()方法被调用. Java中 ...

  6. HAProxy安装及简单配置

    一.HAProxy简介 代理的作用:web缓存(加速).反向代理.内容路由(根据流量及内容类型等将请求转发至特定服务器).转码器(将后端服务器的内容压缩后传输给client端).缓存的作用:减少冗余内 ...

  7. Python Variable Scope

    Python中的变量的作用域有时会让像我这样的初学者很头疼. 其实只需要掌握以下两点: 1. Python能够改变变量作用域的代码段是def.class.lamda;    而if/elif/else ...

  8. JavaScript:学习笔记(6)——New运算符

    JavaScript:学习笔记(6)——New运算符 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例. 快速开始 当你使用new关键字的时候,会 创建一个新的对象 将th ...

  9. Boostrap常用组件英文名

    dropdownlisttabsearchVertical TabSidebar with tabssidebarExpandable Panel ListFiltered Attendees Lis ...

  10. yii2-lock-form 也许这就是你想要的,阻止表单多次提交

    是不是被用户的行为所困扰? 一个表单用户点击提交按钮了N次,这也导致了数据提交了N次. 为了此受到了测试的欺辱,受到了老板的批评? 不用怕,它就是来拯救你的. 第一步:打开命令行,敲入 compose ...