在分布式集群中,如何保证相同请求落到相同的机器上,并且后面的集群机器可以尽可能的均分请求,并且当扩容或down机的情况下能对原有集群影响最小。

round robin算法:是把数据mod后直接映射到真实节点上面,这造成节点个数和数据的紧密关联、后期缺乏灵活扩展。
一致性哈希算法:多增加一层虚拟映射层,数据与虚拟节点映射、虚拟节点与真实节点再映射。

一般都会采用一致性哈希或者hash slot的方法。一致性哈希的ketama算法实现在扩容或down的情况下,需要重新计算节点,这对之前的分配可能会有一些影响。所以可以引入hash slot的方式,即某些hash slot区间对应一台机器,对于扩容或down机情况下,就改变某个hash slot区间就可以了,改动比较小,对之前分配的影响也较小。

虚拟桶是取模和一致性哈希二者的折中办法。

  • 采用固定节点数量,来避免取模的不灵活性。
  • 采用可配置映射节点,来避免一致性哈希的部分影响。

先来看一下hash slot的基本模型:

记录和物理机之间引入了虚拟桶层,记录通过hash函数映射到虚拟桶,记录和虚拟桶是多对一的关系;第二层是虚拟桶和物理机之间的映射,同样也是多对一的关系,即一个物理机对应多个虚拟桶,这个层关系是通过内存表实现的。对照抽象模型章节,key-partition是通过hash函数实现的,partition-machine是通过内存表来实现的。注:couchbase就是利用的此技术。

key对虚拟桶层

虚拟桶层采用预设固定数量,比如可以预设N=1024。意味之后这个分布式集群最大扩容到1024个节点,带来的好处就是mod后的值是不变的(非常重要),这保证了第一层映射不受实际节点变化的影响。 关于最大数量,可根据实现需要预先定义好即可。

虚拟桶对实际节点

举个例子,项目刚开始使用时配置节点映射:
Redis Server1对应桶的编号为0到500。
Redis Server2对应桶的编号为500到1024。
缓存数据量增长后需要增加新节点,在加之前需要重新分配节点对应虚拟桶的编号。 比如增加server3并配置对应桶的编号400到600,这时对于key映射虚拟桶层完全无影响。  实际上mod 400到600的真实数据还在另外两台节点上,请求过来后还会发生无法命中的影响。这就要求在增加新节点前,需要在后台把另外二台的400到600编号数据拷贝到新节点上面,完成后再添加配置到映射上面。 因为新来请求会命中到新节点,所以另外2台的400到600编号数据就无用了,需要进行删除。这种做法就能最大限度(100%)的保证动态扩容后,对缓存系统无影响,具体实现细节后续还需深入进行研究。

在redis集群的设计中也是采用的这个思路。

Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽 (hash slot)的方式来分配的。redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16算法来取模得到所属的slot,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384

所以,我们假设现在有3个节点已经组成了集群,分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是:

  • 节点A覆盖0-5460;
  • 节点B覆盖5461-10922;
  • 节点C覆盖10923-16383.

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

  • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 A 、B 、 C 中的某些槽移动到节点 D 就可以了。

    比如我想新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上。大致就会变成这样:

    • 节点A覆盖1365-5460
    • 节点B覆盖6827-10922
    • 节点C覆盖12288-16383
    • 节点D覆盖0-1364,5461-6826,10923-12287
  • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

另外,还有一个问题:为什么哈希槽的数量固定为16384?(https://github.com/antirez/redis/issues/2576)

由于使用CRC16算法,该算法可以产生2^16-1=65535个值,可是为什么哈希槽的数量设置成了16384?

Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with 16k slots, but would use a prohibitive 8k of space using 65k slots.
        At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.

So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.

总结一下:
1、redis的一个节点的心跳信息中需要携带该节点的所有配置信息,而16K大小的槽数量所需要耗费的内存为2K,但如果使用65K个槽,这部分空间将达到8K,心跳信息就会很庞大。

2、Redis集群中主节点的数量基本不可能超过1000个。

3、Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话,bitmap的压缩率就很低,所以N表示节点数,如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。而16K个槽当主节点为1000的时候,是刚好比较合理的,既保证了每个节点有足够的哈希槽,又可以很好的利用bitmap。

4、选取了16384是因为crc16会输出16bit的结果,可以看作是一个分布在0-2^16-1之间的数,redis的作者测试发现这个数对2^14求模的会将key在0-2^14-1之间分布得很均匀,因此选了这个值。

最后,将redis中计算hash slot的源码贴出来,看一下效果

 #include <iostream>
#include <string.h>
#include "crc16.h" unsigned int keyHashSlot(char *key, int keylen) {
int s, e; /* start-end indexes of { and } */ std::cout << "key : " << key << std::endl; for (s = ; s < keylen; s++)
if (key[s] == '{') break; /* No '{' ? Hash the whole key. This is the base case. */
if (s == keylen) return crc16(key,keylen) & 0x3FFF; /* '{' found? Check if we have the corresponding '}'. */
for (e = s+; e < keylen; e++)
if (key[e] == '}') break; /* No '}' or nothing betweeen {} ? Hash the whole key. */
if (e == keylen || e == s+) return crc16(key,keylen) & 0x3FFF; /* If we are here there is both a { and a } on its right. Hash
* * what is in the middle between { and }. */
return crc16(key+s+,e-s-) & 0x3FFF;
} int main(int argc, char * argv[])
{
if (argc != ) {
std::cout << "usage: ./a.out key" << std::endl;
} char * key = argv[]; std::cout << keyHashSlot(key, strlen(key)) << std::endl; return ;
}

运行结果:

本文参考自:

http://blog.csdn.net/baoxifu/article/details/51344786

http://www.cnblogs.com/mushroom/p/4542772.html

https://www.cnblogs.com/wxd0108/p/5798498.html

http://redisdoc.com/topic/cluster-tutorial.html

https://github.com/antirez/redis/issues/2576

http://blog.onlycatch.com/post/60c42de47e9a

https://www.zhihu.com/question/53927336

hash slot(虚拟桶)的更多相关文章

  1. 探索C#之虚拟桶分片

    阅读目录 背景 虚拟桶(virtual buckets) 实现 总结 背景 关于数据分片讨论最多的是一致性hash,然而它并不是分布式设计中的银弹百试百灵. 在数据稳定性要求比较高的场景下它的缺点是不 ...

  2. 11.redis cluster的hash slot算法和一致性 hash 算法、普通hash算法的介绍

    分布式寻址算法 hash 算法(大量缓存重建) 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡) redis cluster 的 hash slot 算法 一.hash 算法 来了一 ...

  3. 分布式均匀算法--hash性一致算法--hash slot(转)

    目录 1.redis cluster介绍 2.最老土的hash算法和弊端(大量缓存重建) 3.一致性hash算法(自动缓存迁移)+虚拟节点(自动负载均衡) 不用遍历    -->   hash算 ...

  4. redis集群报错:(error) CLUSTERDOWN Hash slot not served

    百度上坑太多,如果你遇到搭建redis集群的时候出现这个错误在百度上找到解决办法基本上都是坑. 首先集群搭建完成后,你肯定去登陆redis进行测试 1.redis01/redis-cli -h &qu ...

  5. redis cluster和hash slot

    redis cluster介绍 从redis3.0.0开始,官方支持了redis cluster的集群模式,结束了redis没有集群的时代. redis cluster 支撑 N 个 redis ma ...

  6. Redis集群环境各节点无法互相发现与Hash槽分配异常 CLUSTERDOWN Hash slot not served的解决方式

    总结/朱季谦 在搭建Redis5.x版本的集群环境曾出现各节点无法互相发现与Hash槽分配异常 CLUSTERDOWN Hash slot not served的情况,故而把解决方式记录下来. 在以下 ...

  7. 一致性hash和虚拟节点

    consistent hashing 算法的原理 consistent hashing 是一种 hash 算法,简单的说,在移除 / 添加一个 cache 时,它能够尽可能小的改变已存在key 映射关 ...

  8. memcached学习——分布式算法(Consistant hash + 虚拟节点)(三)

    1.取余算法 优点:数据分布均匀缺点:当服务器动态的添加.删除节点或者某台server down掉,会导致命中率超大幅度下降,甚至导致服务不可用 2.Consistant Hash算法:一致性哈希算法 ...

  9. 一致性hash算法详解

    转载请说明出处:http://blog.csdn.net/cywosp/article/details/23397179     一致性哈希算法在1997年由麻省理工学院提出的一种分布式哈希(DHT) ...

随机推荐

  1. Google Breakpad 完全解析(一) —— Windows入门篇

    原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢. Google breakpad是一个非常实用的跨平台的崩溃转储和分析模块,他支持Windows,Lin ...

  2. 获取dataset结果集的第一行第一列字段

    DataSet fileNameDs = DbHelper.excuteSqlResultDataSet(strSql); ) { DataTable fileNameDt = fileNameDs. ...

  3. Openshift 节点添加和删除

    1.节点添加 在新节点上编辑yum源/etc/yum.repo.d/ocp.repo /etc/hosts在主和节点上都加上相应信息 编辑host文件,加入 [OSEv3:children] mast ...

  4. 在 Android Studio 2.2 中愉快地使用 C/C++

    转载请注明出处:http://blog.csdn.net/wl9739/article/details/52607010 注:官网上面的技术文章也在不断地汉化中,只是进度有点慢.在我翻译本篇文章的时候 ...

  5. C++静态库与动态库详解

    1 库的概念? 库是写好的现有的,成熟的,可以复用的代码.现实中每个程序都要依赖很多基础的底层库. 2 动态库与静态库的概念? 先回顾一下编译过程: 2.1 静态库 静态库在链接阶段,会将汇编生成的目 ...

  6. python学习:Windows 下 Python easy_install 的安装

    Windows 下 Python easy_install 的安装     下载安装python安装工具下载地址:http://pypi.python.org/pypi/setuptools 可以找到 ...

  7. jersey rest webservice

    参考官网:https://jersey.github.io/documentation/latest/getting-started.html#new-webapp 创建一个 JavaEE Web A ...

  8. Python 面试中 8 个必考问题(转载)

    Python 面试中 8 个必考问题 1.下面这段代码的输出结果是什么?请解释. def extendList(val, list=[]): list.append(val) return list ...

  9. supervise 用来监控服务,自动启动

    Atong介绍的这个工具,挺好用的.supervise 官方网站: https://cr.yp.to/daemontools.html cd /data/test cat test.c #includ ...

  10. javascript 中用到的时间戳函数

    JavaScript 获取当前时间戳:第一种方法: var timestamp = Date.parse(new Date()); 例如结果:1280977330000第二种方法: var times ...