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. Android怎样在http头信息里设置參数

    在使用http请求server时经常要传递一些參数给server.如IMEI号.平台号.渠道号.client的版本等一些通用信息,像这些參数我们没有必要每次都拼在url后,我们能够统一加入到http头 ...

  2. Mac 安装Minikube

    环境信息: guoguo-MacBook-Pro-3:~ guoguo$ docker versionClient: Version:    17.12.0-ce API version:    1. ...

  3. (转)fiddler使用简介--其一

    原文地址:https://www.cnblogs.com/miantest/p/7289694.html Fiddler基础知识 Fiddler是强大的抓包工具,它的原理是以web代理服务器的形式进行 ...

  4. 【zabbix】zabbix忘记密码,重置密码

    忘记密码这种事经常会发生,这里我们介绍一种zabbix忘记用户密码的处理方式. 原理: zabbix存储在数据库中用户名密码是经过32位,小写,md5加密过的.我们可以手动修改数据库中用户的密码. 实 ...

  5. 剑指offer 面试3题

    面试3题: 题:数组中重复的数字 题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内. 数组中某些数字是重复的,但不知道有几个数字是重复的.也不知道每个数字重复几次.请找出数组中任意一个重复 ...

  6. 剑指offer 面试48题

    面试48题:题目:最长不含重复字符的子字符串 题:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长字符串的长度.假设字符串中只包含‘a’-‘z’的字符.例如,在字符串“arabcacfr ...

  7. 常用python模块

    webbrowser浏览器控制模块 主要知道      导入   webbrowser使用webbrowser.open(url)运行就可以在默认浏览器打开指定url 来自为知笔记(Wiz)

  8. Oracle网络服务管理与配置

    一.Oracle网络服务概述 1.网络解决方案. (1)可连接性:在Oracle中,由Oracle net组件负责在客户端应用程序与数据服务器之间创建会话.维护会话连接和数据传输. (2)可管理性: ...

  9. Qt移植对USB鼠标键盘、触摸屏的支持

    .USB键盘 经过一番搜索,发现对Qt键盘的支持主要关系到两个方面: 1. 键盘类型确定: 4.7以前的Qt版本,如果是PS2圆孔键盘,Qt编译时需加上选项:-qt-kbd-vr41xx(未测试):如 ...

  10. imx6qsbd lvds dtc

    lvds显示屏调试参考 1.基于飞思卡尔imxsolosabresd开发板Linux-3.10.53 lvds屏幕调试: http://blog.csdn.net/qq_37375427/articl ...