Redis 主从复制原理分析,这篇就够了

前言

在现有企业中 80%公司大部分使用的是 redis 单机服务,在实际的场景当中单一节点的 Redis 容易面临风险。

由于无法做到故障转移,所以接下来的请求将会直接打到数据库,大量的查询使得数据库连接数达到峰值,且内部锁冲突严重,造成慢查询、连接超时等后果。所以这个时候,我们能不能把数据复制多个副本部署到其他节点上进行复制,当发生故障时,快速地手动切换连接的 Redis?

当然可以的,Redis 就为我们提供了这个功能。

主从模式能将 Master 节点的数据冗余到多台 Slave 上,配合哨兵模式能够快速感知 Master 宕机从而进行主从切换实现故障转移。

主从模式图

主从模式可以分为一主一从、一主多从以及主从从的结构,不管是什么结构,共同点就是每个从节点都会复制主节点的数据,以此来实现数据的备份。

下图为以上 3 种结构的结构图:

一主一从:

  • 一主一从:当主节点出现故障时,从节点转变成主节点继续服务。
  • 一主多从:这种方式通常会做 Redis 读写分离业务,主节点只负责写请求,所有的查询请求都被分配到各个从节点上,这将大大提升 Redis 的吞吐量。我们还可以通过一主多从将比较耗时的命令放到任意的从节点中,这样可以有效的防止慢查询对主节点造成阻塞,从而影响 Redis 服务的稳定性。
  • 主从从:Slave2 并不是作为 Master 的直接从节点,而是间接从节点,这样 Slave2 不需要请求 Master 去复制数据,只需要请求Slave0,这种方式可以有效降低 Master 节点的工作负载。

主从复制原理

Redis 在复制时底层采用的是 psync 命令完成的数据主从同步,同步主要分为:全量复制和增量复制两种

  • 全量复制:顾名思义也就是一次性把主节点数据全部发送给从节点,所以这种情况下,当数据量比较大时,会对主节点和网络造成很大的开销。
  • 部分复制:用于处理主从复制时因网络中断等原因造成数据丢失的场景。当从节点再次和主节点连接时,主节点会补发丢失的数据。因为是补发,所以在发送的数据量一定是小于全量的数据。

概念

在接下来讲解全量复制和部分复制前,需要明确几个概念,在底层使用 psync 命令运行时需要以下几个条件:

复制偏移量

主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播 N 个字节数据时,主节点的 offset 增加 N;从节点每次收到主节点传来的 N 个字节数据时,从节点的 offset 增加 N。

offset 用于判断主从节点的数据库状态是否一致:如果二者 offset 相同,则一致;如果 offset 不同,则不一致,此时可以根据两个 offset 找出从节点缺少的那部分数据。例如,如果主节点的 offset 是1000,而从节点的 offset 是500,那么部分复制就需要将 offset 为501-1000 的数据传递给从节点。而 offset 为 501-1000 的数据存储的位置,就是下面要介绍的复制积压缓冲区。

复制积压缓冲区

复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。

在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。

由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点 offset 的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size);例如如果网络中断的平均时间是 60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为 100KB,则复制积压缓冲区的平均需求为 6MB,保险起见,可以设置为 12MB,来保证绝大多数断线情况都可以使用部分复制。

从节点将 offset 发送给主节点后,主节点根据 offset 和缓冲区大小决定能否执行部分复制:

  • 如果 offset 偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制。
  • 如果 offset 偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制

运行 ID(runid)

每个 Redis 节点在启动时都会生成一个运行 id,即 runid,该 id 用于唯一标识 Redis 节点,它是一个由 40 位随机的十六进制的字符组成的字符串,通过 info server 命令可以查看节点的 runid

从节点在初次建立连接进行全量复制时(从节点发送 psync ? -1),主节点会将自己的 runid 告知给从节点,从节点将其保存起来。当主从节点断开重连时,从节点会将这个runid发送给主节点,主节点会根据从节点发送的 runid来判断选择何种复制:

  • 如果从节点发送的 runid 与当前主节点的 runid 一致时,主节点则尝试进行部分复制,当然能不能进行部分复制还要看偏移量是否在复制积压缓冲区。
  • 如果从节点发送的 runid 与当前主节点的 runid 不一致时,则进行全量复制。

psync命令流程

在了解了复制偏移量、复制积压缓冲区、节点运行 id 之后,下面我们看一下主从节点是如何确定使用全量复制还是部分复制的。

在这里插入图片描述

  • 首先,从节点根据当前状态,决定如何调用 psync 命令:
    • 如果从节点之前未执行过 slaveof 或最近执行了slaveof no one,则从节点发送命令为 psync ? -1,向主节点请求全量复制。
    • 如果从节点之前执行了slaveof,则发送命令为 psync ,其中 runid 为上次复制的主节点的 runid,offset 为上次复制截止时从节点保存的复制偏移量。
  • 主节点根据收到的 psync 命令,及当前服务器状态,决定执行全量复制还是部分复制:
    • 如果主节点版本低于 Redis2.8,则返回 -ERR 回复,此时从节点重新发送 sync 命令执行全量复制。
    • 如果主节点版本够新,且 runid 与从节点发送的 runid 相同,且从节点发送的 offset 之后的数据在复制积压缓冲区中都存在,则回复 +continue,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可。
    • 如果主节点版本够新,但是 runid 与从节点发送的 runid 不同,或从节点发送的 offset 之后的数据已不在复制积压缓冲区中(在队列中被挤出了),则回复 +fullresync ,表示要进行全量复制,其中 runid 表示主节点当前的 runid,offset 表示主节点当前的 offset,从节点保存这两个值,以备使用。
  • 全量复制

    全量复制示意图如下:
    在这里插入图片描述

    全量复制是 Redis 最早支持的复制方式,触发全量复制的命令是 sync 和 psync。下面我们介绍一下 Redis 中全量复制的流程:

    • 发送 psync 命令进行数据同步,由于是第一次进行复制,从节点没有复制偏移量和主节点运行 id,所以发送 psync ?-1。
    • 主节点接收从节点的命令后,主节点根据 psync ? -1 解析出当前为全量复制,所以回复 +FULLRESYNC,同时也会将自身的 runid 和 offset 偏移量发送给从节点,响应为 +FULLRESYNC {runid} {offset}。
    • 从节点接受主节点的响应后,会保存主节点的 runid 和 偏移量 offset<。/li>
    • 主节点执行 bgsave 保存 RDB 文件到本地,主节点发送 RDB 文件给从节点,从节点把接受的 RDB 文件保存在本地,并直接作为从节点的数据文件,接受完 RDB 文件后从节点打印相关日志。
    • 从节点开始接受 RDB 文件到接受完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令保存在客户端的缓冲区中,当从节点加载完 RDB 文件后,主节点在把缓冲区的数据发送给从节点,已保证主从之间的数据一致性。如果主节点和从节点 RDB 文件的数据传输时间过长时,按上面分析可能会造成客户端的缓冲区溢出。默认配置为 client-output-buffer-limit slave 256MB 64MB 60。 如果 60 秒内缓冲区消耗大于 64MB 或者超过 256MB 时,主节点将直接关闭客户端连接,造成全量同步失败。
    • 从节点接受完主节点传送来的全部数据后会清空自身的旧数据。
    • 从节点清空数据后开始加载 RDB 文件,如果 RDB 文件比较大时,该操作也是比较耗时的。
    • 从节点成功加载完 RDB 文件后,如果当前节点开启了 AOF 持久化功能,它会立刻做 bgrewriteaof 操作,为了保证全量复制后 AOF 持久化文件立刻可用

    全量复制主要耗时地方如下:

    • 主节点通过 bgsave 命令 fork 子进程进行 RDB 持久化,该过程是非常消耗 CPU、内存(页表复制)、硬盘 IO 的。
    • 主节点通过网络将 RDB 文件发送给从节点,对主从节点的带宽都会带来很大的消耗。
    • 从节点清空老数据、载入新 RDB 文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行 bgrewriteaof,也会带来额外的消耗。

    其它场景应该尽量必免使用全量复制。由于 Redis 全量复制有种种弊端,所以 Redis 提供了部分复制功能,下面我们看一下 Redis 中的部分复制功能。

    部分复制

    部分复制示意图如下:
    在这里插入图片描述

    部分复制主要是 Redis 针对全量复制的过高开销做出的一种优化措施,使用 psync{runId}{offset}命令实现,当主从节点在命令传播节点发生了网络中断,出现数据丢失情况,则从节点会向主节点请求发送丢失的数据,如果请求的偏移量在复制积压缓冲区中,则主节点就将剩余的数据补发给从节点,保持主从节点数据一致,由于补发的数据一般都会比较小,所以开销相当于全量复制而言也会很小,流程如下:

    • 当主从节点之间网络出现中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并中断复制连接。
    • 由于主节点没有宕机,所以他依然会响应客户端命令,但因复制连接中断命令无法发送给从节点,但主节点内部会将这段时间的命令保存在客户端缓冲区中,默认大小为 1MB。
    • 当主从节点网络恢复后,从节点会再次连接上主节点。
    • 当主从节点连接恢复后,由于从节点之前保存了自身的偏移量和主节点的运行 id。因此会把它们当作 psync 参数发送给主节点,要求进行部分复制操作。
    • 主节点接受从节点的 psync 命令,会先核对请求的 runid 是否和自身的的 runid 一致,如果一致,说明该从节点复制的当前主节点。然后查看请求的 offset 是否在复制积压缓冲区,如果在则进行部分复制,否则进行全量复制,部分复制回复 +continue 响应,从节点接受回复
    • 在进行部分复制时,主节点只需要根据 offset 将复制积压缓冲区的数据发送给从节点,保证主从复制进入正常状态

    心跳

    在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:ping 和 replconf ack。心跳机制对于主从复制的超时判断、数据安全等有作用。

    • 主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信,通过 client list 命令查看复制客户端信息,主节点的连接状态为 flags = M,从节点连接状态为 flags = S。
    • 主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态,可能通过 repl-ping-slave-period 参数修改发送频率。
    • 从节点在主线程中每隔 1 秒发送 replconf ack{offset} 命令,给主节点上报自身当前的偏移量。

    总结

    本文主要讲解了主从复制之间的复制原理,分为:全量复制和部分复制。Master 区分是全量复制还是部分复制,依靠的是 runid 与 offset 参数。

    第一次复制,则进行全量复制,Master 生成 RDB 文件,并将之后生成的命令存入复制积压缓冲区。Slave 断线重连后,Master 依据 offset 在复制积压缓冲区中选择传输的起始字节。如果找不到则进行全量复制。

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值