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. 客户端-服务器通信安全 sign key

    API接口签名校验,如何安全保存appsecret? - 知乎  https://www.zhihu.com/question/40855191 要保证一般的客户端-服务器通信安全,可以使用3个密钥. ...

  2. sql server扫盲系列

    本系列为入门级,不会介绍过于深入的知识.为防止不道德转载(特别是红黑联盟,把我原文地址删掉,其他照搬,无节操无道德),尽可能打上水印和加上原文地址,读者看的不爽请见谅.原文地址:http://blog ...

  3. boost之网络通信

    ip::tcp的内部类型socket,acceptor以及resolver是TCP通信中最核心的类. 1.同步客户端代码: #include <iostream> #include < ...

  4. shell方式切割tomcat日志

    #!/bin/bash while true do cd /usr/local/tomcat/logs d=`date +%Y%m%d` d15=`date -d'15 day ago' +%Y%m% ...

  5. HBA卡 和 RAID卡

    HBA卡: 只从HBA的英文解释HOST BUS ADAPTER(主机总线适配器)就能看出来,他肯定是给主机用的,一般HBA就是给主机插上后,给主机扩展出更多的接口,来连接外部的设备.大多数讲到HBA ...

  6. 剑指offer 面试24题

    面试24题: 题目:反转链表 题:输入一个链表,反转链表并输出反转后链表的头节点. 解题思路:注意反转时出现断裂现象,定义3个指针,分别指向当前遍历到的节点pNode.它的前一个节点pPrev及后一个 ...

  7. War-ftpd USER longString漏洞攻击之Java实现常见问题

    发表这篇文章的最重要原因是,在用Java实现War-ftpd缓冲区溢出实验时,我遇到了很多问题,而且我认为这些问题 都是非常不容易发现和解决的,为了以后学习的同学的便利,此处写下自己遇到的问题作为分享 ...

  8. MySQL忘记密码的正确解决方法

    MySQL忘记密码解决方案:破解本地密码: Windows: 1.用系统管理员登陆系统. 2.停止MySQL的服务. 3.进入命令窗口,然后进入 MySQL的安装目录,比如我的安装目录是c:\mysq ...

  9. asp.net 利用Response.Filter 获取输出内容, 变更输出内容

    重写 Response.Filter 就可以获取或更新输出到浏览器的内容       资料: https://weblog.west-wind.com/posts/2009/Nov/13/Captur ...

  10. 运维角度浅谈MySQL数据库优化

    一个成熟的数据库架构并不是一开始设计就具备高可用.高伸缩等特性的,它是随着用户量的增加,基础架构才逐渐完善.这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分 ...