Redis Cluster自己提供了自动将数据分散到Redis Cluster不一样节点的能力,分区实现的关键点问题包括:如何将数据自动地打散到不一样的节点,使得不一样节点的存储数据相对均匀;如何保证客户端可以访问到正确的节点和数据;如何保证从新分片的过程当中不影响正常服务。这篇文章经过了解这些问题来认识Redis Cluster分区实现原理。node
Redis Cluster是由多个同时服务于一个数据集合的Redis实例组成的总体,对于用户来讲,用户只关注这个数据集合,而整个数据集合的某个数据子集存储在哪一个节点对于用户来讲是透明的。Redis Cluster具备分布式系统的特色,也具备分布式系统如何实现高可用性与数据一致性的难点,由多个Redis实例组成的Redis Cluster结构一般以下:redis
Redis Cluster算法
Redis Cluster特色以下:数组
Redis Cluster中有一个16384长度的槽的概念,他们的编号为0、一、二、3……1638二、16383。这个槽是一个虚拟的槽,并非真正存在的。正常工做的时候,Redis Cluster中的每一个Master节点都会负责一部分的槽,当有某个key被映射到某个Master负责的槽,那么这个Master负责为这个key提供服务,至于哪一个Master节点负责哪一个槽,这是能够由用户指定的,也能够在初始化的时候自动生成(redis-trib.rb脚本)。这里值得一提的是,在Redis Cluster中,只有Master才拥有槽的全部权,若是是某个Master的slave,这个slave只负责槽的使用,可是没有全部权。Redis Cluster怎么知道哪些槽是由哪些节点负责的呢?某个Master又怎么知道某个槽本身是否是拥有呢?缓存
Master节点维护着一个16384/8字节的位序列,Master节点用bit来标识对于某个槽本身是否拥有。好比对于编号为1的槽,Master只要判断序列的第二位(索引从0开始)是否是为1便可。分布式
位序列spa
如上面的序列,表示当前Master拥有编号为1,134的槽。集群同时还维护着槽到集群节点的映射,是由长度为16384类型为节点的数组实现的,槽编号为数组的下标,数组内容为集群节点,这样就能够很快地经过槽编号找到负责这个槽的节点。位序列这个结构很精巧,即不浪费存储空间,操做起来又很便捷。代理
这里讲的是Redis Cluster如何将键空间分布在不一样的节点的,键空间意为Redis Cluster所拥有用户全部数据集合的键的取值范围,这个范围叫作键空间。提到空间分布,必然会想到哈希算法,没错,经过哈希算法再加上取模运算能够将一个值固定地映射到某个区间,在这里,这个区间叫作slots,区间由连续的slot组成。在Redis Cluster中,咱们拥有16384个slot,这个数是固定的,咱们存储在Redis Cluster中的全部的键都会被映射到这些slot中,下面讲讲Redis Cluster是如何作映射的。code
键到slot的基本映射算法以下:索引
HASH_SLOT = CRC16(key) mod 16384
用Redis中的代码表示以下(这个代码被稍微修改了一下,后面会还原):
crc16(key) & 0x3FFF
通过简单的计算就获得了当前key应该是存储在哪一个slot里面,值得注意的是,指定的key会被存储在哪一个slot,这个关系是铁打不变的。若是我提交了一批命令,往Redis中存储一批键,那么这些键通常会被映射到不一样的slot,而不一样的slot又可能由Redis Cluster中不一样的节点服务,这样就和的预期有点不一样,有没有办法将这批键映射到同一个slot呢?答案是能够。
键哈希标签是一种可让用户指定将一批键都可以被存放在同一个槽中的实现方法,用户惟一要作的就是按照既定规则生成key便可,这个规则是这样的,若是我有对于同一个用户有两种不一样含义的两份数据,我只要将他们的键设置为下面便可:
abc{userId}def和ghi{userId}jkl
redis在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样因为上面两个不一样的键,{}里面的字符串是相同的,所以他们能够被计算出相同的槽,相关代码以下:
unsigned int keyHashSlot(char *key, int keylen) { int s, e; for (s = 0; s < keylen; s++) if (key[s] == '{') break; if (s == keylen) return crc16(key,keylen) & 0x3FFF; for (e = s+1; e < keylen; e++) if (key[e] == '}') break; if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; return crc16(key+s+1,e-s-1) & 0x3FFF; }
客户端是怎么在Redis Cluster中找到正确的节点的呢?下面看看。
文章开始讲到,Redis Cluster并不会代理查询,那么若是客户端访问了一个key并不存在的节点,这个节点是怎么处理的呢?好比我想获取key为msg的值,msg计算出来的槽编号为254,当前节点正好不负责编号为254的槽,那么就会返回客户端下面信息:
GET msg -MOVED 254 127.0.0.1:6381
表示客户端想要的254槽由运行在IP为127.0.0.1,端口为6381的Master实例服务。若是根据key计算得出的槽刚好由当前节点负责,则当期节点会当即返回结果。这里明确一下,没有代理的Redis Cluster可能会致使客户端两次链接急群中的节点才能找到正确的服务,推荐客户端缓存链接,这样最坏的状况是两次往返通讯。
从新分片意为槽到集群节点的映射关系要改变,不变的是键到槽的映射关系,所以当从新分片的时候,若是槽中有键,那么键也是要被移动到新的节点的。下面看看从新分片是怎么作的,假如咱们有一批槽须要从一个Master节点移动到另外一个Master节点:
槽迁移示意图
这里简化模型,假设这批待迁移的槽编号为一、二、3,并假设左边的节点为MasterA节点,右边的节点为MasterB节点。
槽迁移的过程当中有一个不稳定状态,这个不稳定状态会有一些规则,这些规则定义客户端的行为,从而使得Redis Cluster没必要宕机的状况下能够执行槽的迁移。下面这张图描述了咱们迁移编号为一、二、3的槽的过程当中,他们在MasterA节点和Master节点中的状态。槽一、二、3在MasterA节点中的状态为MIGRATING,在MasterB节点中的状态为IMPORTING。
槽迁移中间状态
本例中MIGRATING状态是发生在MasterA节点中的一种槽的状态,预备迁移槽的时候槽的状态首先会变为MIGRATING状态,这种状态的槽会实际产生什么影响呢?当客户端请求的某个Key所属的槽处于MIGRATING状态的时候,影响有下面几条:
本例中的IMPORTING状态是发生在MasterB节点中的一种槽的状态,预备将槽从MasterA节点迁移到MasterB节点的时候,槽的状态会首先变为IMPORTING。IMPORTING状态的槽对客户端的行为有下面一些影响:
键空间迁移是指当知足了槽迁移前提的状况下,咱们就能够经过相关命令将槽一、二、3中的键空间从MasterA节点转移到MasterB节点,这个过程真正实现数据转移。相关命令:
MIGRATE
MIGRATE命令经过三步将数据转移,示意图以下:
键空间迁移步骤
通过上面三步能够将键空间数据迁移,而后再将处于MIGRATING和IMPORTING状态的槽变为常态便可,完成整个从新分片的过程。然而MIGRATE并非原子的,若是在MIGRATE出现错误的状况可能会致使下面问题:
本文介绍Redis Cluster分区实现原理主要关注三个问题,1)数据是如何被自动分散到不一样的节点的;2)客户端是如何可以正确找到节点的;3)键空间迁移过程是怎么样的?其次是一些实现小细节,经过了解这些问题更好地了这些问题从而更好地认识Redis Cluster分区功能。