分片机制在redis中的实践

为什么要做数据分片

随着业务不断的发展,业务系统的请求访问量,数据量都在不断增长,此时业务系统也要进行架构升级,来支撑业务增长。对于高并发请求和海量数据存储,比较常用的解决方案之一就是做分片。

分片的思想比较简单:一个人干不完的活,那就多个人干,一个容器装不完的东西,分多个容器来装。是不是很简单?

虽然思想很简单,但是落地到工程实践上,需要考虑的细节就变多了:

1.一个容器装不完的东西,以怎样的规则分配到其他容器中?

2.取东西的时候,应该从哪个容器里取?

3.如果某个容器出现了故障,不好用了,该怎么办?

在这里读者可以自己思考一下,如果当你面试遇到类似的问题,你会怎么回答呢?

其实分片思想在很多开源软件中都有应用,例如:redis,ES,kafka等。只不过这些中间件解决的问题不同,在分片思想的具体实现过程中侧重点会存在一些的差异。但是无论哪种实现方式,都是经过海量数据和多种业务场景验证过的,这些实现方案,完全可以当做分片思想的最佳实践。当然,用来回答面试官的问题,更是游刃有余。

下面我们以redis为例,来看看redis的作者是如何对redis做分片的,又是如何解决上面三个问题的?

redis是怎么做数据分片的

使用过redis的老铁都知道,reids主要用来做缓存,利用计算内存访问效率高的特点,来提升查询的性能。内存虽然访问效率高,但是容量有限,我们常用的服务器内存规格是16G,好一点的大概32G左右,但是,这些存储空间,对于一些中小型业务系统来说也是完全不够的。

此时,有些老铁可能会觉得:既然32G不够,那就搞两个32G的内存上去,或者更多的内存条,这里我们先不讨论服务器的主板上是否有这么多的内存插槽。虽然,这种做法的确可以解决存储不够的问题,但是这种做法对于redis来说,却会有很多不足:

1.单实例redis内存过大,不利于实例启动。因为实例启动需要将持久化到磁盘的数据加载到内存中,在这个过程中,数据量越大,加载过程也耗时,实例启动也就越慢。

2.不利于做持久化。我们知道在redis中有一种rdb的持久化方式,它存储的是redis中内存快照,对于大内存的redis实例来说,在进行rdb的时候,会产生大量的磁盘IO,而且比较耗时。

3.主从数据同步以及故障转移效率会降低。

对于大量数据存储的问题,redis在3.0版本以后,给出的解决方案是:切片集群

在redis的切片集群中,对于以上三个问题,都给出了较为巧妙的解决方案。

数据如何分布

对于数据的分配与存储,redis提出了哈希槽的概念。哈希槽是数据存放的基本单元,在一个redis集群中,共有16384个哈希槽,这些哈希槽分布到redis集群中的多个redis实例上,这里的每个redis实例被称为一个分片

当客户端写数据时,首先需要确定数据key应该存放到哪个哈希槽中,然后再确定这个哈希槽应该分布在哪个redis实例上。最终访问这个redis实例,将数据写到目的哈希槽中,完成一次数据写入。

在这个数据写入过程中,有两个问题需要关注:

1.数据key和哈希槽的映射关系。

2.哈希槽和redis实例的对应关系。

对于第一个问题,redis客户端会对数据key,按照CRC16算法计算一个16bit的数值,然后,再用这个16bit的数值对16384取模,每个模数对应一个哈希槽编号,这样就解决了key和哈希槽的映射关系。

对于第二个问题,默认情况下,当redis集群启动时,会将哈希槽均匀分配到所有redis实例分片上,如果集群中有N个实例,那么每个实例就有 16384/N 个哈希槽。

除此之外,redis也提供了相关命令,支持手动分配哈希槽,提供该命令的原因在于:redis均匀分配哈希槽的策略,可能是"不公平"的,什么意思呢?虽然均匀分配会使每个redis实例分配到相同个数的哈希槽,但是每个redis实例的配置可能不同,对于一些配置比较低的实例来说,均匀分配到的哈希槽个数,可能会"过多"。

这里我们举例说明一下手动分配哈希槽命令,redis集群中有3个实例,假设有5个哈希槽,我们可以使用如下命令:

redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

实例 1 保存哈希槽 0 和 1,实例 2 保存哈希槽 2 和 3,实例 3 保存哈希槽 4,在实际应用中,如果采用手动分配哈希槽的话,需要将16384个哈希槽全部分配完才可以,否则redis集群将无法正常工作。

同时我们将 key1 和 key2存储到redis集群中,具体操作如下图:
在这里插入图片描述

数据如何定位

通过上文我们知道了数据在redis集群中是如何分配的,下面我们来看一下,客户端是如何根据这个分配规则来读写数据的?

数据和哈希槽之间的映射关系,主要依靠固定的算法来确定的,客户端按照这个算法直接进行计算即可,这个相对比较简单。

而对于哈希槽在redis实例上的分布情况,可能是redis自动分配的,也有可能是运维同学手动分配的,这里没有一个固定的算法可以确定,那么客户端该如何确定这个分布情况呢?

在redis刚启动的时候,每个redis实例,只知道自己被分配到的哈希槽信息,在每个实例启动后,各个实例之间会通过心跳交互,将自身分配的哈希槽信息同步给和自己连接的redis实例,因为整个集群中的实例之间有连接关系,一个实例可以通过其他实例连接到集群中任何一个实例,因此每个实例最终都会有所有实例上哈希槽的分布情况。

此时当客户端连接任何一个redis实例,都可以通过这个实例获取所有实例的哈希槽分布情况,同时客户端会将这个分布信息缓存在本地,这个时候,客户端就可以通过 数据key和哈希槽的映射关系,以及哈希槽在redis实例上分布情况,精准定位每个key。

数据分片出现了问题怎么办

客户端通过缓存所有哈希槽分布信息,来完成数据的定位,然而,哈希槽在redis实例上的分布,并不是一成不变的,导致分布变化的最常见原因有两个:

1.reids集群中实例个数的变化。比如对集群进行扩容,增加实例个数。这个时候,就需要将其他实例上的哈希槽,转移到这个新增加的实例上,或者当某个实例被检测到健康情况异常,就需要将整个实例上的哈希槽转移到其他实例上去。

2.某些实例压力比较大,比如可能某个实例的负载过重,需要重新对哈希槽进行重新分配。

经过哈希槽的重新分配后,此时各个redis实例上的哈希槽分布和redis客户端本地的缓存信息就产生了数据不一致。

对于这种不一致问题,redis提供了重定向机制,所谓的“重定向”,就是指:客户端给一个实例发送数据读写操作时,如果这个实例上并没有相应的数据,那么客户端要再给一个新实例发送操作命令。那客户端又是怎么知道重定向时的新实例的访问地址呢?

当客户端把一个键值对的操作请求发给一个实例时,如果这个实例上并没有这个键值对映射的哈希槽,那么,这个实例就会给客户端返回 MOVED 命令作为响应结果,这个结果中就包含了新实例的访问地址。

我们假设实例A上的哈希槽H,已经迁移到了实例B上,客户端本地缓存的信息仍然是哈希槽H在实例A上,那么此时客户端访问存储在哈希槽H上的数据key

GET hello:key
(error) MOVED H 实例B:6379

其中,MOVED 命令表示,客户端请求的键值对所在的哈希槽 H,实际是在 实例B 上。
MOVED命令的含义,就是告诉客户端:你刚刚访问的哈希槽,现在已经迁移到 B 这个实例上了,此时客户端就会更新本地缓存,后续再访问这个哈希槽的话,就会直接请求 B 这个实例了。
在这里插入图片描述

上面这种情况是刚好赶上哈希槽迁移完毕后,被客户端访问,如果哈希槽在迁移过程中被客户端访问的话,会是怎样的呢?

如果客户端访问的哈希槽,正在从一个实例迁移到另外一个实例上的话,那么这个哈希槽中的数据就分布在两个redis实例上,比如哈希槽H,分布在实例A和实例B上,此时客户端本地缓存信息为:哈希槽H分布在实例A上,那么客户端就会直接访问实例A,如果访问的数据刚好在实例A上,数据会直接返回,如果访问的数据已经迁移到实例B上的话,那么实例A就会给客户端响应一个ASK报错信息,如下:

GET hello:key
(error) ASK H 实例B:6379

这个报错信息表示:哈希槽H正在做迁移,你所访问的数据已经迁移到 实例B上了。此时客户端会向实例B发送一个ASKING命令,这个命令表示:让实例B执行接下来的命令,接下来客户端就会向实例B发送GET请求,以读取数据。具体过程可以参考下图:
在这里插入图片描述

因为不确定数据何时可以迁移完毕,此时客户端不会更新本地缓存中哈希槽H的分布信息,当再次访问哈希槽H时,仍然会访问实例A。

对重定向比较熟悉的老铁,可能会想到http协议中也有重定向的概念,在http中,重定向分为临时重定向(302)和永久重定向(301)分别对应这里的MOVED和ASK。

到这里,相信你对redis中数据分片机制,有了一个更深的了解,有兴趣的读者,可以在了解一下ES的分片机制是如何实现的,可以参考“Elasticsearch学习-ES中的一些组件介绍”,相信你会有更大的收获。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值