redis集群扩容和缩容_深入理解Redis Cluster集群

一、背景

前面的文章《深入理解Redis哨兵机制》一文中介绍了Redis哨兵集群的工作原理,哨兵集群虽然满足了高可用的特性,但是依然存在这样的问题:即数据只能往一个主节点上进行写入。

只能往一个主节点上进行写入会有什么问题呢?大家都知道,其实在很多大型分布式系统中,要缓存的数据往往是非常大的,可能会达到几十GB,几百GB,甚至上TB的数据需要缓存。那么这种情况下,单节点写入的架构可能就无法满足业务的要求了,即使几十GB的,我们虽然可以通过纵容扩容的方式来提高单机的内存容量,但是通过《深入理解Redis持久化》一文,我们了解到,单实例的Redis的不宜内存过大,否则数据持久化将可能导致性能问题。

那么这个时候,既然纵容扩容已经遭遇了瓶颈,那么是否有其它横向扩容的解决方案呢?其实Redis已经为我们提供了横向扩容的解决方案,那就是Redis Cluster集群。在早期的Redis版本(3.0之前),Redis是不支持Cluster解决方案的。那个时候,如果我们需要使用Redis分布式存储的方案,我们只能自己手工对数据进行分片。

本文章的内容,我们将研讨Redis Cluster集群的工作原理及对Redis Cluster集群的一些常见问题进行剖析。

二、核心原理

比如说现在有这样一个场景,我们在18GB的数据要进行存储,我们手里有3台8G的机器,我们分别编号为:节点1、节点2、节点3。这个时候,我们应该怎么做呢?

这里我们就需要解决这样两个问题:

(1)、这18G的数据,我们应该如何将它平均分摊到这3个机器上;

(2)、查询数据的时候,我们去哪台机器上查找我们要查询的数据。

对于数据的分布式存储,我们通常要先对要进行分布式存储的数据,先选定一个用于数据分片的键,通常称为片键(sharding key),业内目前比较常见的处理手段有这么几种:

a、按片键的值固定取模映射到不同节点;

b、按片键的值分区间存储映射到不同节点;

c、将片键的值用一致性哈希算法均摊到各个节点上。

我们先来说说按片键取模的方案a,比如说我们如下userid:1,2,3,4,5,6,7,8,9......N-2, N-1, N。这里我们假设N % == 0。

节点1:userid % 3 == 0 (存储3, 6, 9...N)

节点2:userid % 3 == 1 (存储1, 4, 7... N-2)

节点3:userid % 3 == 2 (存储2, 5, 8...N-1)

这种方案有什么好处,映射算法非常简单;聪明的你可能也看出来了这个算法的坏处,那就是如果增加节点的数量或减少节点的数量。就会涉及到大量数据的移动,几乎每个节点上的数据都需要重新进行求模运算,这个过程叫rehash。然后根据rehash的结果,将数据重新分布在各个实例上。

那么接下来,你也可以考虑这样的映射算法,也是方案b:

节点1:1, 2 ... N/3

节点2:N/3+1 ... (2*N/3)

节点3:(2*N/3)+1 ... N

这样按片键的区间,比如说每台机器存储一个区间,这样进行映射,是不是可以呢?理论上这样映射也是可以的。而且这个映射算法的好处,就是增加新节点的时候,它虽然不需要移动数据,但是新增加的节点,刚开始可能存储的数据比较少,也是会出现新增加的节点在存储压力的分担上与其它已经存在的节点不均衡的现象,这种不均衡的现象要直到它的数据被填满(即达到分配给他的存储区间)。另外,如果要减少一个实例,因为其它的每一个节点都是存储固定范围的数据,所以一旦节点被减少,那么就需要重新划定每一个节点新的范围,这里就会涉及到大量的数据移动。

OK,上面我们分析了按片键的值固定取模、按片键的值分区间映射两种方案,它们都存在着这样的一些问题:

(1)、扩容(取模)或缩容(取模、分区间)的时候,容易导致大批量的数据移动;

(2)、扩容的时候,新增加节点往往压力与旧节点不均衡。

为了解决上述的分片缺点,那么Redis是怎么做的呢?

在Redis Cluster集群方案中,它采用了方案c的一个变种,它引入了slot的概念,什么呢slot呢?slot就是哈希槽的意思,redis把整个集群划分为16384个哈希槽,具体操作上有两个大的步骤:

1、把数据根据片键的值映射到具体的某一个slot上;

2、然后再在内部维护一个slot到实例节点的映射关系。

接下来,我们先来看看第一个步骤,如下图所示:

56ea3f642ecb1729778ffa26f6645c72.png

Redis会对sharding key,按照CRC16算法计算一个16bit 的值;然后,再用这个16bit值对16384取模,得到 0~16383范围内的模数,每个模数代表一个相应编号的哈希槽。上图中的N==16383。关于CRC16哈希算法,如果你有兴趣了解,可以自行查阅相关的文档。

那么接下来就是第2个问题这些slot是如何映射到机器上的呢?其实在redis的内部,它是维护了一个slot到实例的映射表,如下图所示:

be9e8821e5965b85cc7a59c8c855f9cb.png

这是一个双向映射关系表,根据slot可以计算出它对应的节点,根据节点可以知道它分配了哪些slot。

通过上述的两个步骤,Redis Cluster就完成了数据的分布以及查询请求的路由,不管是数据的分布还是路由都是这样两个步骤:

(1)、先根据sharding key计算出slot;

(2)、再根据节点与slot的映射表,查到slot对应的节点。

要了解它的优点,我们就需要了解一致性哈希算法(带虚拟节点的)的优点。关于什么是带虚拟节点的一致性哈希算法,这里我们先不展开,读者有兴趣,可以自行了解一下。

我们先给出这样做在应对扩容或缩容的时候,它会表现出哪些特点,主要从数据迁移代价以及各实例压力分担情况两个维度来进行分析,我们假设有N台机器:

1、扩容(增加1个节点)

数据迁移代价:平均每台机器移动1/N的数据到新节点上。

各实例压力分担:均衡分担。

2、缩容(减少1个节点)

数据迁移代价:平均每台机器增加存储被减少实例的1/N的数据。

各实例压力分担:均衡分担。

综合来看,我们的方案c,也就是Redis Cluster集群虽然在扩容与缩容的时候,也会涉及到数据的移动,但是无论哪一种情况,它都可以保持整个集群的相对稳定,各实例都可以按预定的计划进行均衡的数据存储,以及读写压力的均衡分担。

另外,redis提供了一个参数cluster-enabled,将该参数设置为yes,那么节点将以Redis Cluster集群的模式运行。

三、常见问题

上面我们已经介绍完了Redis Cluster集群的核心工作原理,下面我们来看看该集群的一些问题:

问题1:如果各个实例要部署的机器内存容量不一样怎么办?

对于这个问题,其实Redis Cluster是提供了手动配置slot到实例的映射关系的,这样性能高的机器可以多配置一些slot,性能低一点的机器可以配置少一些slot的,但是需要满足这样两个条件:1、尽可能均衡分配(这样才可以保证扩容与缩容时,数据迁移代价及各实例读写分担相对于实例性能整体平衡);2、在手动配置映射关系表的时候,必须将16384个slot全部配置完,否则集群无法正常工作。

问题2:客户端是怎么知道各实例与slot的映射关系的呢?

Redis在集群搭建好了之后,每一个客户端都会缓存映射关系表,因为最终客户端取数据是要向具体的某一个redis实例去请求数据。

问题3:比如说有这样一种情况,由于集群的扩容与缩容,会导致数据的迁移,那么对于客户端来说,它有一个键值对,我们比如说叫:hello(key) - world(value)。它之前是存储在节点1中,由于数据的迁移,这个key-value对就迁移至节点2了。那么这个时候,客户端是怎么知道去节点2拿数据的呢?

Redis Cluster集群中的实例之间,会保护通信,以相互之间同步映射关系表信息,那么针对这种有key被迁移走了的情况,其实redis提供了一种重定向的机制来解决问题,如下图所示:

98ca6153ec1059ccc291ef82c057e4d7.png

大概的步骤如上图所示:

(1)、客户端之前根据hello计算出的slot所映射的机器是节点1;

(2)、由于集群扩容增加了节点2,hello这个key的值被移动到了节点2;

(3)、由于迁移的过程的,客户端并不知道,于是客户端继续向节点1请求hello的这个key;

(4)、节点1计算这个hello的slot,根据最新的映射表查询到该slot对应的节点是节点2;

(5)、节点1向客户端响应一条moved命令,响应内容包含了hello当前所在的节点2的ip和port;

(6)、客户端收到moved命令后,更新本地的映射表(实例到slot的映射表);

(7)、客户端再次向节点2发起请求;

(8)、节点2返回hello对应的value,也就是将world返回给客户端。

问题4:在实际应用中,针对问题3中客户端先向节点1去查询,如果恰好,hello这个key正在迁移中,此时会出现一种情况,会出现什么情况?

对于这种部分迁移完成的情况,在客户端向节点1发起请求,要读取hello的value时,节点1发现这个key正在迁移中,那么节点1会返回一个ASK命令的错误响应给客户端,如下:

(error) ASK 10238 192.168.1.102:6379

这个10238,比如说就是代表hello这个key的哈希槽编号,也就是slot的编号,相当于节点1告诉客户端,hello这个key正在迁移中,你去问问节点2,看看节点2是否现在已经完成了这个key的存储。

然后客户端会先向节点发送ASKING命令,通知节点2允许自己在节点2中继续查询hello的值。

这里有一个问题需要注意的是:在接受到节点1的ASK命令时,虽然也返回了节点2的ip和port,但客户端此时并不会去更新本地的映射表。只有最后从节点2在接受ASKING的命令时,返回给了客户端肯定的响应之后,客户端才会更新本地映射表。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值