Redis 主从复制

什么是 Redis 主从复制

单机 Redis 存在单点风险问题,也就是如果 Redis 节点出现宕机,那么会导致大量请求直接到 MySQL 数据库,严重的情况下可能会让 MySQL 数据库宕机

为了解决单台 Redis 可能宕机的问题,提出了副本的概念,也就是复制多个 Redis 的副本,部署在不同的服务器上,如果其中一台宕机,有备用 Redis 可以顶上,这就是故障转移

这也是 Redis 具有高可用的基础,原 Redis 节点被称为主节点 (master) ,复制的 Redis 节点被称为从节点 (slave)。每个从节点只能有一个主节点,但每个主节点可以用多个从节点 (1 -> n)

理论情况下,主从节点中的数据应该无时无刻都具有一致性。但是复制的数据流是单向的,只能从主节点复制到从节点,所以只有主节点可以读写数据,而从节点只可以读数据,否则就会导致主从节点不一致

Redis 主从节点的关系如下图所示:

8

如何配置主从复制

注意:如果没有任何其它的关系存在,每个节点都是主节点

建立主从复制关系,这个过程会删除从节点当前所有数据,然后对新主节点进行复制操作,使主从数据一致

断开主动复制关系,这个过程会断开与主节点的复制关系,重新变回主节点

如果 6380 已经是某主节点的从节点,再执行slaveof 127.0.0.1 6379命令会经历四个阶段,也就是切换主节点的流程:

可以通过info replication命令查看当前节点与复制相关的信息

主从复制过程

上一部分从客户端的角度介绍了如何配置才能让两个 Redis 节点建立主从复制关系。本部分更近一步,介绍当客户端输入slaveof ip port命令后,Redis 节点之间会经历的过程,流程如下图:

9

保存主节点信息:从节点保存主节点的地址信息就直接返回,此时建立复制流程还没有开始

主从建立 socket 连接:从节点内部通过每秒运行的定时任务,发现存在新主节点后会尝试和它建立网络连接,专门用于接收主节点发送的复制命令。如果连接失败,定时任务会无限重试直至成功

发送 ping 命令:发送 ping 请求进行首次通信,检测主从之间 socket 是否可用,以及检测主节点是否可以接收处理命令

权限验证:主节点可以通过requirepass设置密码,如果设置了该参数,需要进行密码验证

同步数据集:主从节点可以正常通信后,进行首次复制操作,主节点会把持有的数据全部发送给从节点,这部分也是耗时最长的步骤

命令持续复制:同步完成后,主节点依旧会执行客户端的写命令,为了主从一致性会持续将写命令发送给从节点

主从数据同步

在上一部分的六个步骤中,只有同步数据集是最耗时的,因为它耗时,所以就会对它不停的改进优化,所以就变成了最麻烦的一步。下面分别从三个时间点来讨论同步方案的迭代过程

Redis 2.8 之前的 sync 方案

在 Redis 2.8 之前都是基于 sync 命令的全量复制,整个步骤如下:

10

注意:主节点会为每一个从节点单独开辟一块 replication buffer (复制缓冲区) 来记录执行 bgsave 命令后主节点接收的写命令

从上面的步骤可以看出 sync 命令是一个非常耗费资源的操作,体现在如下方面:

重点:如果由于网络等原因导致主从节点断开连接,会造成从节点数据丢失。当从节点再次连接上主节点,需要重新进行上述的全量复制过程,极其耗费资源

这里抛出一个面试问题:为什么全量复制选择 RDB 而不是 AOF?这个其实是想让你比较 RDB 和 AOF 的区别,更具体点是比较优缺点,更详细可见 RDB 和 AOF 比较

可以从三个方面回答:文件大小、数据恢复速度、AOF 刷盘策略 (如果策略选择不当,会影响 Redis 正常运行,更详细可见 AOF 文件同步)

Redis 2.8 之后的 psync 方案

psync 命令相比于 sync 多了两个参数

复制偏移量:主从节点都维护了一个复制偏移量,在执行完命令后,会将命令的字节累加到复制偏移量中。主节点中同时保存了自身和从节点的复制偏移量,可以通过偏移量判断主从节点数据是否一致

不仅如此,psync 还新增了复制积压缓冲区,在主节点中保存了一个固定长度的队列,默认 1MB,当有从节点连接主节点时被创建,所有从节点共用一个复制积压缓冲区

在完成全量复制后,主节点只需要持续传播命令给从节点即可。在传播的过程中,不仅需要将命令发送给从节点,还需要加入到复制积压缓冲区,如果缓冲区满了,就遵循 FIFO 原则弹出最久入队的命令

如果主从节点断开连接导致部分数据丢失,先根据偏移量去复制积压缓冲区找,如果存在,就执行部分复制,只需要将复制积压缓冲区对应的命令发送给从节点即可,如果不存在,就执行全量复制

注意:复制缓冲区在全量复制阶段使用,每个从节点都有一个;复制积压缓冲区在命令持续传播阶段使用,所有从节点共享一个

11

上图的右边,如果主节点返回+fullresync {runId} {offset}表示从节点将触发全量复制;如果主节点返回+continue表示从节点将触发部分复制

如果主节点返回-err表示主节点版本低于 Redis 2.8,无法识别 psync 命令,从节点将发送旧版的 sync 命令触发全量复制

根据上面的流程图可以知道有两种情况会触发全量复制,判断复制积压缓冲区是否存在相应命令以及是否为首次复制,前者比较好判断,那后者如何判断呢?

psync 的第一个参数 runId,如果没有则默认值为?,第二个参数 offset,如果是第一次参与复制则默认值为 -1。所以如果发送命令psync ? -1可以判断为首次复制

还有另外一种情况,如果 runId 和当前主节点 runId 不一致,表示从节点中存的数据为另外一个主节点的,也需要进行全量复制

到此为止分析了 psync 和 sync 的区别,以及触发全量复制和部分复制的情况,下面给出两种复制的流程图:

12

虽然 psync 相比于 sync 新增了部分复制来减少资源的消耗,但下面两种本应该可以部分复制的情况却只能全量复制:

Redis 4.0 的 psync2.0 方案

psync2.0 针对于上一部分最后提出的两个缺陷都有了相应的解决办法,使得可以进行部分复制。针对于缺陷一,从节点生成的 RDB 文件中包含了主从复制的相关信息

对于缺陷二,psync2.0 舍弃了 runId 的概念,取而代之的是 replid 和 replid2

还有两个和偏移量有关的字段

全量复制:从节点发送的 replid 和当前主节点的 replid 不同 && (从节点发送的 replid 和当前主节点的 replid2 不同 || 从节点发送的偏移量和当前主节点的 second_replid_offset 不一致)

主从复制对过期键的处理

从节点的过期删除动作由主节点控制:

在 Redis 3.2 之前,客户端对从节点执行读命令可能会返回过期数据;在 Redis 3.2 及之后,从节点返回数据前会先检查是否过期,如果过期就返回空值,所以不会读到过期数据

注意:在 Redis 3.2 及之后不会返回过期数据,但也不会删除过期数据,只是在返回前判断一波是否过期

主从复制的痛点

当主节点宕机后,需要进行故障转移,这个过程需要人工干预,不过还好有哨兵 (Sentinel) 可以帮助自动化故障转移 (高可用)

主从复制在高并发场景下能力有限,如果数据量过大,主从复制也无法满足要求,毕竟主从都是一样的,起不到太大作用,这个时候集群 (cluster) 就出现了

运维中存在的问题

Redis 主从复制中从节点可以有多个,这些从节点副本可以用于读写分离、故障转移、实时备份等场景,但在实际应用复制功能时,也存在一些坑!!

读写分离

由于主节点可以执行读写命令,而从节点只能执行读命令,而且从节点时主节点的副本,所以可以实现读写分离,让读命令都去从节点上执行,而写命令都去主节点上执行,可以大大减少主节点的压力

可是使用读写分离时,也可能会遇到以下问题:

主从配置不一致

主从只允许持久化配置不一致,例如:对写命令并发高的场景,主节点使用 RDB,从节点使用 AOF

对于内存相关的配置必须保持一致,因为从节点是主节点的一个副本,如果内存不一致,容易导师从节点数据丢失

规避全量复制

当对数据量比较大且流量高的主节点添加从节点时,尽量在低峰时进行操作,因为添加节点会触发第一次全量复制

复制积压缓冲区不足,会导致从节点断开重连后偏移量不在该缓冲区中,触发全量复制。建议根据系统实际情况,设置合适大小的复制积压缓冲区

规避复制风暴

单主节点复制风暴:主节点重启,从节点会发起全量复制,如果从节点过多,会导致网络带宽流量开销过大

单机器复制风暴:由于 Redis 是单线程,会在同一台机器上部署多个 Redis 实例。如果都是主节点,会导致大量从节点的复制,消耗大量的资源,建议主节点分布在不同的机器上

参考文章