Redis——主从复制

目录

配置主从架构

[Redis 搭建主从架构的三种方式](#Redis 搭建主从架构的三种方式)

断开主从结构

主从架构的拓扑结构

一主一从拓扑​

一主多从拓扑

树形拓扑

主从复制(数据同步)

同步的载体和进度表示

实时复制

全量复制

部分复制

更多

replid2

psync

传输延迟

主从复制总流程

查看主从结构信息


Redis支持主从架构。使用主从架构,可以提高服务并发量,也可以提高服务可用性。但是主从架构涉及到一个关键的问题就是主从复制(即主节点和从节点之间的数据同步问题),本篇文章对此展开讲解

  • 关于提高服务并发量方面:主要是提高了读操作并发量,因为主节点(可以写的节点)只有一个,全部的写操作仍旧由一个节点承担。这也是没有办法的事,因为如果有多个主节点的话,数据同步会更加麻烦(不过可以配合其他方案来解决这个问题)。
  • 关于提高可用性方面:在主从架构中一个主节点负责写,多个从节点负责读。一但从节点挂了,对整个服务几乎没有影响;一但主节点挂了,服务仍旧可读,只是暂时不能写,不至于全都挂了。

配置主从架构

Redis 搭建主从架构的三种方式

以下三种方式都可以将某一个redis服务变成另一个redis服务的从节点,masterHost和masterPort分别表示主节点服务的IP和端口号

  1. 配置文件方式
    在 Redis 配置文件(如 redis.conf)中添加以下指令,重启 Redis 后永久生效:
    slaveof {masterHost} {masterPort}

  2. 启动命令参数方式
    在启动 Redis 服务时,通过命令行参数指定主节点信息,仅对本次启动生效:
    redis-server --slaveof {masterHost} {masterPort}

  3. 运行时动态指令方式
    连接至 Redis 客户端后,执行以下命令即时生效,无需重启(重启后失效):
    slaveof {masterHost} {masterPort}

配置完成后,从节点不再能够修改数据(可以在配置文件中修改slave-read-only来解除限制,但是一般不这样做)。

断开主从结构

复制代码
slaveof no one

执行上述命令的客户端所连接的redis服务不再是从节点。如果想要永久生效那就把配置文件的相关配置内容删除后重启服务即可。


主从架构的拓扑结构

一主一从拓扑

在这种结构下,主节点可能因为写需求过于频繁而压力过大,此时可以关闭主节点的AOF模式来缓解压力但是这样一来,如果主节点崩溃,不能立即重启:主节点重启后内存中的数据可能会丢失很多,从节点一但同步主节点会把很多数据也删除掉。因此如果主节点崩溃,应该让主节点使用从节点的AOF文件进行重启而不是直接重启**。**

当然具体情况还得根据业务来看,如果说redis只是作为提高性能的缓存,并且对速度不敏感,也就直接重启了。

一主多从拓扑

这种结构也存在主节点并发压力大的问题,同时还存在主节点带宽压力大的问题,因为主节点要给多个从节点同步数据

树形拓扑

这种结构也存在主节点并发压力大的问题,但是缓解了主节点带宽压力大的问题,因为把同步任务分派给了各级非叶子节点而不只是主节点。不过也引入了同步延迟大导致数据不一致更加明显的问题,因为是逐级同步的。


主从复制(数据同步)

主从复制大致有三种复制方式,它们不是三种排他的选择,而是共同构成了redis主从复制系统

同步的载体和进度表示

主节点和从节点要进行数据同步,必然需要主节点给从节点传输一些什么,这里就有两种选择:

  1. 二进制数据
  2. 主节点执行过的命令

**如果选择二进制数据的话,一但主节点不是添加键值对而是修改原有键值对,主节点就需要告知从节点哪个位置被修改了(这又涉及到了位置问题)?修改成了什么?否则,**每次数据同步,主节点都需要把所有数据都发给从节点来保证数据一致。

而选择命令的话,主节点只需要不断把自己执行过的命令让从节点也执行一遍即可,其他的都不用考虑。

因此,把命令作为同步的载体是最高效,最自然的选择。节点累积执行的命令长度(字节数,以下称为offset)就可以表示该节点的同步进度或者总的进度(如果是主节点)。

主节点会将最近执行过的命令以及到该命令为止的offset一一对应保存到backlog缓冲区内,主要是为全量复制和部分复制的触发提供了判断依据(间接保证了主从同步的可靠性)以及为部分复制提供了需要复制的部分命令,下面读者会有所体会

为什么backlog不仅保存命令还要保存到该命令为止的offset?

backlog缓冲区是有限的,一但命令积压过多之前的命令就会被覆盖,但是这些命令可能并没有被从节点同步,这就造成了主从节点数据不一致却不自知的问题。有了"到该命令为止的offset"之后,从节点可以用自己的offset和缓冲区命令对应的offset对比,如果判断发现自己的offset相对于缓冲区中最小offset差了不止一个命令长度的话就说明丢失命令了,此时就要触发全量复制。同时,可以通过偏移量知道哪些命令已经复制,哪些没有复制,支持部分复制。

实时复制

实时复制是由主节点向从节点发起的、目标是实时同步少量数据的报文

主从节点成功建立复制连接后,主节点会将自身接收到的所有数据修改操作,通过 TCP 长连接持续同步给从节点(并不断增加双方offset表示进度);从节点执行同步过来的命令修改本地数据,以此保证主从之间的数据一致性。

该复制长连接依靠应用层自研心跳包维持链路存活(并非 TCP 协议自带的保活心跳),完整心跳检测规则如下:

  1. 主、从节点双向实现心跳检测逻辑,双方都会以客户端身份向对方发起通信;
  2. 主节点默认每 10 秒向从节点发送ping命令,用于探测从节点是否存活、复制链路是否正常;
  3. 从节点默认每 1 秒向主节点发送 replconf ack {offset} 指令,上报自身当前已同步完成的复制偏移量(offset);

超时断开规则: 若主节点检测到与从节点的通信延迟超过配置项 repl-timeout(默认 60 秒),会判定从节点已离线,主动断开该复制客户端连接;待从节点重新建立复制连接后,心跳检测流程自动恢复。

全量复制

全量复制是由从节点向主节点发起的、目标是一次性同步主节点所有数据的请求

  1. 从节点向主节点发起全量复制的请求
  2. 主节点解析请求,发送一个+FULLRESYNC响应,其中包含主节点自身的运行时信息(这个用于部分复制,之后会解释)
  3. 从节点把主节点发来的运行时信息保存下来
  4. 主节点使用bgsave,创建新的子进程来把调用bgsave那一刻主节点的内存数据以及主节点那一刻的offset保存成RDB文件,在此过程中,主节点还可以接受客户端发来的命令并执行,这些命令会被缓存一份在backlog缓冲区里,主节点offset持续增加。
  5. 主节点向从节点发送RDB文件
  6. 主节点向从节点发送backlog缓冲区里的最新命令(它们修改的内容是前面的RDB文件所没有包含的)
  7. 从节点读取RDB文件中的数据到内存里,并使用RDB文件中保存的offset更新自己的offset
  8. 从节点读取命令同步数据,并不断增加自己的offset
  9. 如果从节点开启了AOF,在全量复制过后,从节点会主动进行AOF的重写,缩小AOF的体积(因为可能一下子新增了大量数据)

Redis 从 2.8.18 版本开始支持无磁盘复制。主节点在执行 RDB 生成流程时,不会生成 RDB 文件到磁盘中了,而是直接把生成的 RDB 数据通过网络发送给从节点。这样就节省了一系列的写硬盘和读硬盘的操作开销。

不过即使这样,全量复制仍旧是一种很耗费资源(无论是网络带宽还是主从节点的CPU、内存等资源)的复制方式

为什么使用RDB文件,不是说命令才是同步载体吗?

在全量复制这种大数据量的网络传输,RDB这种压缩二进制文件很节省资源,况且,一旦涉及到全量复制很可能是从节点缺很多,这种情况下主节点的backlog也存不下那么多的命令。

部分复制

部分复制发生在特定的情况下,是一种避免全量复制的优化手段:当主从节点之间因为网络抖动而断开TCP连接,从节点重连主节点成功后不必进行全量复制,而是可以接着上次连接继续同步数据,这样就节省了很多资源,提高了效率。

现在的问题就是从节点怎么知道重连后的主节点还是原来的主节点(如果不是原来的主节点还继续同步而不全量复制,主从节点的数据就会不一致)?毕竟,我们可以把任何一个节点变为主节点。解决这个问题的方法就是replicationid:

replicationid(replid)是一个40位长度的随机字符串每个主节点都会维护一个replid(无论它本来就是主节点,还是从节点变成主节点)。维护replid的目的是唯一地标识一个历史数据集**。当主从节点第一次建立连接的时候,主节点把自己的replid发送给从节点,从节点将其保存下来,等到从节点重连成功后它会首先发送这个replid给新主节点,如果新主节点的replid和这个相同,说明还是原来的主节点(历史数据集),如果不是,说明不是原来的主节点,这个时候就要进行全量复制了。**

除此之外,在断开连接的整个过程中,主节点有可能又执行了许多命令,如果这些命令把之前从节点没有同步的、存放在backlog缓冲区的命令覆盖了,这时候从节点只能全量复制主节点而不能触发部分复制。所以该如何进一步判断是否能触发部分复制呢,解决这个问题的方法就是offset:

从节点重连成功后不仅仅发送replid给主节点,还发送从节点此时的offset,如果从节点的offset小于backlog缓冲区中最小的命令对应的offset,说明已经有几个命令被新命令覆盖了,需要进行全量复制,反之,可以继续部分复制

综上,部分复制的整个流程如下:

  1. 当主从节点之间出现网络中断时,如果超过 repl-timeout 时间,主节点会认为从节点故障并终端复制连接。
  2. 主从连接中断期间主节点依然响应命令,但这些复制命令都因网络中断无法及时发送给从节点,所以暂时将这些命令滞留在backlog中。
  3. 当主从节点网络恢复后,从节点再次连上主节点。
  4. 从节点将之前保存的 replicationId 和 复制偏移量发送给主节点,请求进行部分复制(全量复制的时候要主节点发送给从节点的运行时信息)。
  5. 主节点进行必要的验证:
    1. replicationid与主节点的replicationid是否相同,如果相同进行下一步,否则响应++FULLRESYNC并退出流程
    2. offset是否小于backlog缓冲区内命令对应的最小offset,如果否,响应+CONTINUE(这是进行部分复制的标志),否则响应++FULLRESYNC并退出流程
  6. 主节点将需要从节点同步的数据发送给从节点,最终完成一致性。

更多

replid2

其实主节点除了维护replid之外,还维护一个replid2。简单来说,一个从节点晋升为主节点之后,会自己生成一个replid,同时把上一个主节点的replid存在replid2字段中。这样有一个好处:即使新的主节点是由从节点晋升而来,其他从节点和其同步的时候也有一定概率触发部分复制(如果offset符合要求),因为他们原来同属一个复制拓扑,数据很相近。这进一步提升了效率。

当然,在判断的流程中要把判断条件从"replid == 主节点replid"变成 "replid == 主节点replid || replid == 主节点replid2"

这就引出了另一个知识点,其实从节点也可能会维护backlog缓冲区,为自己晋升为主节点做准备(有了backlog就有更大可能触发部分复制而不是全量复制以此提升效率)

psync

上述全量复制和部分复制其实都是服务器通过psync命令来发起的:

bash 复制代码
psync replication offset
  • 如果想全量复制,输入psync ? -1;
  • 如果想尝试部分复制,输入psync replid offset(replid是从节点保存的数据集标识符,offset是从节点当前的同步进度)

传输延迟

主从节点一般部署在不同机器上,复制时的网络延迟就成为需要考虑的问题,Redis 为我们提供了 repl-disable-tcp-nodelay 参数用于控制是否关闭 TCP_NODELAY,默认为 no,即开启 TCP_NODELAY 功能,说明如下:

  • 当为no时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机房部署。

  • 当为yes时,主节点会合并较小的 TCP 数据包从而节省带宽。默认发送时间间隔取决于 Linux 的内核,一般默认为 40 毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂的场景,如跨机房部署。

主从复制总流程

第一次建立连接,从节点向主节点发送psync ? -1,一次性同步所有数据。之后主节点不断向从节点发起实时复制,当遇到突发状况的时候从节点会先发送psync replid offset 尝试部分复制以提高效率,如果不行则进行全量复制,之后又一次进入实时复制 ... ....


查看主从结构信息

bash 复制代码
info replication
相关推荐
长不胖的路人甲1 小时前
Redis 缓存的数据持久化方案讲解
数据库·redis·缓存
长不胖的路人甲1 小时前
Redis 单线程为什么速度很快
数据库·redis·缓存
韩楚风1 小时前
【参天引擎】Cantian 服务端框架全景解析:进程架构、模块组成与交互关系
数据库·mysql·架构·cantian
Listen·Rain2 小时前
数据库流式查询
java·数据库
彦为君2 小时前
算法思维与经典智力题
java·前端·redis·算法
雨辰AI2 小时前
生产级实战:人大金仓 V9 标准化运维手册(日常巡检 + 监控告警 + 应急处置)
java·运维·数据库·后端
阿拉斯攀登3 小时前
向量数据库选型:Milvus vs Chroma vs Elasticsearch
数据库·elasticsearch·milvus·知识库·rag·个人知识库
彦为君3 小时前
Redis最新版本特性
java·数据库·redis·算法·bootstrap
vigor5123 小时前
MySQL通过Mango实现分库分表
android·数据库·mysql