数据增多了,该加内存还是加实例?【切片集群】

假想一下,如果要用 Redis 保存 5 亿个键值对,平均每个键值对是 512 B,那么大概这些键值对所占的空间是 25 G,一种方案是选择更大内存的服务器,比如 32 G,这样就能保存所有的数据,同时留有 7G 的内存,可以保证系统的正常运行。但这样 Redis 服务器运行久了会出现响应速度慢的问题。使用 INFO 命令查看 latest_fork_usec(表示最近一次 fork 的耗时),发现持久化的时间特别长,快到秒级别了。

这跟 Redis 的持久化机制有关,在使用 RDB 持久化时,Redis 会 fork 子进程来完成,fork 操作的用时和数据量是正相关的,而 fork 在执行时会阻塞主线程。数据量越大,fork 操作造成的主线程阻塞的时间就越长。所以当对 25G 的数据进行持久化的时候,就会导致 Redis 响应变慢。

所以第一种方案不行,这时候就需要第二种方案,也就是 Redis 的切片集群。组建 Redis 切片集群比较麻烦,但它可以保存大量的数据,而且对 Redis 主线程的阻塞影响较小。

切片也叫分片,就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。比如把 25G 平均成 5 份,那么每个实例只需要保存 5G 数据。如下图所示:
在这里插入图片描述
这样在切片集群中,内存为 5 G的实例在生成 RDB 时,数据量就小了很多,fork 子进程一般不会给主线程带来较长时间的阻塞。采用多实例保存数据分片后,既保存了 25 G数据,又避免了 fork 子进程阻塞主线程而导致的响应变慢。

如何保存更多的数据?

上面两种方案是应对 Redis 数据量增多的方案,分别对应着纵向扩展和横向扩展。

  • 纵向扩展:升级单个 Redis 实例的资源配置,包括内存、磁盘、cpu等。
  • 横向扩展:增加 Redis 实例的个数。

第一种方式的优点是因为只有单个实例,数据存在哪,客户端就去哪里访问,操作简单。但在持久化过程中,随着内存增加主线程阻塞时间也会增加;同时纵向扩展还会受到硬件和成本的限制。

第二种方式的优点是只需要增加 Redis 实例的个数即可。不用担心单个实例的硬件和成本限制,在面对百万、千万级别的用户规模时,Redis 分片集群是一个非常好的选择。缺点就是不可避免要考虑到多个实例分布式管理的问题,也就是:

  • 数据切片后,在多个实例之间如何分布?
  • 客户端怎么确定想要访问的数据在哪个实例上?

数据切片和实例的对应关系

首先切片集群只是一种通用机制,用来保存大量数据的方案,这个机制可以有不同的实现方案。Redis 官方中也提供了一种方案:Redis Cluster。

Redis Cluster 方案采用的是哈希槽(Slot)来处理数据和实例之间的映射关系。共分为 16384 个哈希槽,每个键值对根据它的 key 的 哈希值被映射到一个哈希槽中。

映射过程分两步:首先根据 key 按照 CRC16 算法计算一个 16 bit 的值;然后将这个值对 16384 取模得到 0~16383 范围内的模数,每个模数代表一个哈希槽。

那么这些哈希槽是如何映射到具体的 Redis 实例上的呢?

在部署 Redis Cluster 方案时,使用 cluster create 命令创建集群,Redis 会自动把这些槽平均分布在集群实例上。例如有 N 个实例,每个实例槽数为 16384 / N。

也可以使用 cluster meet 手动建立实例间的连接,再使用 cluster addslots 指定每个实例上的哈希槽个数。适合在集群中不同 Redis 实例内存大小不一致的情况。如果把哈希槽平均在每个实例上,在保存相同数量的键值对时,和内存大的实例相比,内存小的实例就会有更大的容量压力。所以遇到这种情况,就需要根据不同实例的资源配置,使用 cluster addslots 手动分配哈希槽。
在这里插入图片描述

redis-cli -h host –p port cluster addslots 0,1
redis-cli -h host –p port cluster addslots 2,3
redis-cli -h host –p port cluster addslots 4

特别注意,在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。

客户端如何定位数据?

定位键值对数据时,它所处的哈希槽是可以通过计算得到的,这个计算是在客户端发送请求时执行。但是,但是哈希槽分布在哪个实例上还需要进一步定位。

一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是在集群刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。

那么,怎么使得客户端可以在访问任何一个实例的时,都能获得所有的哈希槽信息呢?因为 Redis 实例会把自己的哈希槽信息发给和它相连接的其他实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。

当客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键值对所对应的哈希槽,然后就可以给相应的实例发送请求了。但是在集群中,实例和哈希槽的对应关系并不是一成不变的,最常见的变化有两个:

  • 当实例新增或删除时,Redis 需要重新分配哈希槽;
  • 为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。

此时,实例之间可以通过相互传递信息,来获得最新的哈希槽分配信息,但是客户端无法感知到这些变化,它缓存的信息就会和最新的分配信息不一致。

Redis Cluser 提供了一种重定向机制,就是当客户端给一个实例发送数据读写操作时,这个实例上并没有相应的数据,客户端要再给一个新实例发送操作命令。

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

GET hello:key
(error) MOVED 13320 172.16.19.5:6379

这个命令表示,客户端所请求的键值对在哈希槽 13320,实例在 172.16.19.5 上。这样客户端就可以直接和这个新实例建立连接并发送操作请求。如下图所示:
在这里插入图片描述
在上图中,当客户端给实例 2 发送命令时,Slot 2 中的数据已经全部迁移到实例 3。实际上如果实例 2 中数据比较多,就可能出现一种情况:客户端向实例 2 发送请求,但此时 Slot 2 中只有一部分迁移到了实例 3,还有部分数据没有迁移。这种情况下客户端就会收到一条 ASK 信息,如下所示:

GET hello:key
(error) ASK 13320 172.16.19.5:6379

这条命令表示客户端请求的键值对所在的哈希槽在 13320,在 172.16.19.5 这个实例上,但是这个哈希槽正在迁移。此时,客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令。这个命令的意思是,让这个实例允许执行客户端接下来发送的命令。然后客户端再向这个实例发送 GET 命令读取数据。
在这里插入图片描述
和 MOVED 命令不同的是,ASK 命令并不会更新客户端缓存的哈希槽分配信息。如果客户端再次请求 Slot 2 中的数据,它还是会实例 2 发送请求。这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值