【redis】redis实战主从原理以及生产情景:Redis数据增多了,是该加内存还是加实例?

  本站以分享各种运维经验和运维所需要的技能为主

《python零基础入门》:python零基础入门学习

《python运维脚本》: python运维脚本实践

《shell》:shell学习

《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战

《k8》从问题中去学习k8s

《docker学习》暂未更新

《ceph学习》ceph日常问题解决分享

《日志收集》ELK+各种中间件

《运维日常》运维日常

《linux》运维面试100问

《DBA》db的介绍使用(mysql、redis、mongodb...)

redis 进阶篇

一、数据同步:主从库如何实现数据一致?

Redis实现数据持久化有AOF和RDB两个方法,但是即使有这两个方法,也还是存在服务不可用的问题,为啥子嘞?

假设你只有一台服务器,然后这台机器宕机了,这个时候他会以上面的两种方式恢复数据,但是在恢复数据的过程中,新来的数据怎么办?是不是就服务不到了,

Redis之所以牛逼,一方面是因为他快,另一方面是因为高可靠性。所谓的高可靠性就是指,尽量的少丢数据,和服务端尽量少中断。AOF和RDB保证了前者,但是不能保证后者。那咋办呢?

方法肯定是有的,Redis针对和服务端少中断这个问题是这么处理的,将一份数据保存在多个实例上。这样子,即使一台机器完蛋,其他机器也可以接着进行服务,不会导致整个Redis不能使用。

这样子是不是就解决了一开始的问题?我就知道你说是,确实是解决了,但是还有有问题。这么多机器,如何保证每台机器数据的一致性呢?而且客户端的读写操作可以下发给每一台机器吗?

读写分离肯定要依赖主从库喽,所以Redis有个解决的方法,那就是采用主从库,主库负责写,然后数据同步给从库,客户端读取数据则可以从任意一个库中读取(其实就是从任意一台机器读取)。

主-从模式

主从一致性原理

在主从中,通常的操作是主库用来写入数据,从库用来读取数据。这样的好处是避免了所有的请求压力都打在了主库上,同时系统的伸缩性也得到了很大的提升。

但是问题就来了,读从库时的数据要与主库保持一致,那就需要主库的数据在写入后同步到从库中。如何保持主库与从库的数据一致性,当有多个从库时,又如何做到呢?

1、全量复制

这是第一次同步时所发生的传递关系。看名字就知道,主库第一次就毫无保留的把所有数据都传递给了从库。

图中的同步流程已经很清晰了,总共分为三部分:

(1)主从节点建立联系

当从节点与主节点第一次建立联系时,从节点会向主节点发送 psync 命令,表示要进行数据同步。

正如你看到的 psync 命令后会带有两个参数:一个是 runID,一个是偏移量 offset。

runID:每个Redis实例生成的随机且唯一的ID,在这里表示的是主节点的ID。

offset:复制偏移量。

在图中第一次复制时因为不知道主库ID和偏移量,因此用“?”和“-1”分别来表示runID 和 offset。

当主节点接收到 psync 命令后,会使用 FULLSYNC命令向从节点发送 runID 及offset 两个参数。从节点将其信息保存下来。

到这里关系算是建立了下来。

(2)主节点同步RDB文件

RDB文件,这是一个老面孔了,持久化时会用到的二进制文件。在这里起着主从数据同步的作用,也就是说主从同步是依赖 RDB 文件来实现的。

从节点接收到 RDB 文件后,在本地完成数据加载,算是完成了主从同步。

我们回想下 RDB 文件是如何生成的。在持久化那篇文章里,我们介绍过,父进程 fork 了一个子进程来进行生成 RDB 文件。父进程并不阻塞接收处理客户端的命令。

那么问题就产生了,当主节点把 RDB 文件发送给从节点时,主节点同时接收的命令又该如何来处理?

(3)主节点同步缓冲区命令

这一步就是来解决 RDB 文件生成后,父进程又接收到写命令同步的问题的。

为了保证主从节点数据的一致性,主节点中会使用缓冲区来记录 RDB 文件生成后接收到的写操作命令。在 RDB 文件发送完成后会把缓冲区的命令发送给从节点来执行。

到这里,主从节点的数据同步算是完成了。

2、级联操作

我们再来回顾下整个同步流程,从建立关系,生成 RDB 文件,传输给从节点到最后缓冲区命令发送给从节点。这是一个从节点与主节点同步的完整流程。

那么我们再来思考:当有多个从节点,也就是一主多从时,第一次连接时都要进行全量复制。但是在生成 RDB 文件时,父进程 fork 子进程时可能会出现阻塞,同时在传输 RDB 文件时也会占用带宽,浪费资源。

这种情况我们该如何来解决呢?

主-从-从模式。通过对从节点再建立从节点。同步数据时从级联的从节点上进行同步,从而就减轻了主节点的压力。

网络开小差了

上面的流程我们已经知道了正常情况下主从节点的复制过程了,但是当网络中断导致主从连接失败等异常情况下,主从同步又是如何来进行的?

在这里要提到一个增量复制的名词,与全量复制不同的是,它是根据主从节点的偏移量来进行数据同步的。

什么意思呢?

还记得在全量复制里我们所提到过的缓冲区吗?就是用来存储生成 RDB 文件后的写命令的,这里我们称为缓冲区A。主从节点断开连接后,除了会将后续接收到的写命令写入缓冲区A的同时,还会写入到另一个缓冲区B里。

在缓冲区B里,主从节点分别会维护一个偏移量 offset。刚开始时,主节点的写位置与从节点的读位置在同一起点,随着主节点的不断写入,偏移量也会逐渐增大。同样地,从节点复制完后偏移量也在不断增加。

当网络断开连接时,从节点不再进行同步,此时主节点由于不断接收新的写操作的偏移量会大于从节点的偏移量。当连接恢复时,从节点向主节点发送带有偏移量的psync 命令,主节点根据偏移量来进行比较,只需将未同步写命令同步给从节点即可。

总结

主从一致性原理

从节点第一次进行连接时,主节点会生成 RDB 文件进行全量复制,同时将新写入的命令存储进缓冲区,发送给从节点,从而保证数据一致性;

为了减少数据同步给主节点带来的压力,可以通过从节点级联的方式进行同步。

网络开小差了,网络断连重新连接后,主从节点通过分别维护的偏移量来同步写命令。

二、Redis数据增多了,是该加内存还是加实例?

一个需求:要用 Redis 保存 5000 万个键值对,每个键值对大约是 512B,为了能快速部署并对外提供服务,我们采用云主机来运行 Redis 实例,那么,该如何选择云主机的内存容量呢?

粗略地计算了一下,这些键值对所占的内存空间大约是 25GB(5000 万 *512B)。所以,当时,我想到的第一个方案就是:选择一台 32GB 内存的云主机来部署 Redis。因为 32GB 的内存能保存所有数据,而且还留有 7GB,可以保证系统的正常运行。同时,我还采用 RDB 对数据做持久化,以确保 Redis 实例故障后,还能从 RDB 恢复数据。

但是,在使用的过程中,我发现,Redis 的响应有时会非常慢。后来,我们使用 INFO 命令查看 Redis 的 latest_fork_usec 指标值(表示最近一次 fork 的耗时),结果显示这个指标值特别高,快到秒级别了。

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

看来,第一个方案显然是不可行的,我们必须要寻找其他的方案。这个时候,我们注意到了 Redis 的切片集群。虽然组建切片集群比较麻烦,但是它可以保存大量数据,而且对 Redis 主线程的阻塞影响较小。

切片集群,也叫分片集群,就是指启动多个 Redis 实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。回到我们刚刚的场景中,如果把 25GB 的数据平均分成 5 份(当然,也可以不做均分),使用 5 个实例来保存,每个实例只需要保存 5GB 数据。如下图所示:

那么,在切片集群中,实例在为 5GB 数据生成 RDB 时,数据量就小了很多,fork 子进程一般不会给主线程带来较长时间的阻塞。采用多个实例保存数据切片后,我们既能保存 25GB 数据,又避免了 fork 子进程阻塞主线程而导致的响应突然变慢。

在实际应用 Redis 时,随着用户或业务规模的扩展,保存大量数据的情况通常是无法避免的。而切片集群,就是一个非常好的解决方案。这节课,我们就来学习一下。

如何保存更多数据?

在刚刚的案例里,为了保存大量数据,我们使用了大内存云主机和切片集群两种方法。实际上,这两种方法分别对应着 Redis 应对数据量增多的两种方案:纵向扩展(scale up)和横向扩展(scale out)

  • 纵向扩展:升级单个 Redis 实例的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的 CPU。就像下图中,原来的实例内存是 8GB,硬盘是 50GB,纵向扩展后,内存增加到 24GB,磁盘增加到 150GB。

  • 横向扩展:横向增加当前 Redis 实例的个数,就像下图中,原来使用 1 个 8GB 内存、50GB 磁盘的实例,现在使用三个相同配置的实例。

那么,这两种方式的优缺点分别是什么呢?

首先,纵向扩展的好处是,实施起来简单、直接。不过,这个方案也面临两个潜在的问题。

第一个问题是,当使用 RDB 对数据进行持久化时,如果数据量增加,需要的内存也会增加,主线程 fork 子进程时就可能会阻塞(比如刚刚的例子中的情况)。不过,如果你不要求持久化保存 Redis 数据,那么,纵向扩展会是一个不错的选择。

不过,这时,你还要面对第二个问题:纵向扩展会受到硬件和成本的限制。这很容易理解,毕竟,把内存从 32GB 扩展到 64GB 还算容易,但是,要想扩充到 1TB,就会面临硬件容量和成本上的限制了。

与纵向扩展相比,横向扩展是一个扩展性更好的方案。这是因为,要想保存更多的数据,采用这种方案的话,只用增加 Redis 的实例个数就行了,不用担心单个实例的硬件和成本限制。在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。

不过,在只使用单个实例的时候,数据存在哪儿,客户端访问哪儿,都是非常明确的,但是,切片集群不可避免地涉及到多个实例的分布式管理问题。要想把切片集群用起来,我们就需要解决两大问题:

  • 数据切片后,在多个实例之间如何分布?

  • 客户端怎么确定想要访问的数据在哪个实例上?

接下来,我们就一个个地解决。

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

在切片集群中,数据需要分布在不同实例上,那么,数据和实例之间如何对应呢?这就和接下来我要讲的 Redis Cluster 方案有关了。不过,我们要先弄明白切片集群和 Redis Cluster 的联系与区别。

实际上,切片集群是一种保存大量数据的通用机制,这个机制可以有不同的实现方案。在 Redis 3.0 之前,官方并没有针对切片集群提供具体的方案。从 3.0 开始,官方提供了一个名为 Redis Cluster 的方案,用于实现切片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。

具体来说,Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot),来处理数据和实例之间的映射关系。在 Redis Cluster 方案中,一个切片集群共有 16384 个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中。

具体的映射过程分为两大步:

  • 首先根据键值对的 key,按照CRC16 算法计算一个 16 bit 的值;

  • 然后,再用这个 16bit 值对 16384 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。

关于CRC16 算法,如果感兴趣!可以自行Googel查询

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

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

当然, 我们也可以使用 cluster meet 命令手动建立实例间的连接,形成集群,再使用 cluster addslots 命令,指定每个实例上的哈希槽个数。

客户端如何定位数据?

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

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

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

客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。

总结

上述讲述切片集群在保存大量数据方面的优势,以及基于哈希槽的数据分布机制和客户端定位键值对的方法

在应对数据量扩容时,虽然增加内存这种纵向扩展的方法简单直接,但是会造成数据库的内存过大,导致性能变慢。Redis 切片集群提供了横向扩展的模式,也就是使用多个实例,并给每个实例配置一定数量的哈希槽,数据可以通过键的哈希值映射到哈希槽,再通过哈希槽分散保存到不同的实例上。这样做的好处是扩展性好,不管有多少数据,切片集群都能应对。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值