《解锁Redis系列4》主从复制

喜欢有帮助记得点赞哦,加关注不迷路

一、主从复制介绍

1.1、概述

主从复制是将一台Redis服务器的数据,复制到其他Redis服务器上,前者称为主节点(master/leader),后者称为从节点(slave/follower),一个主节点可以有多个从节点,但是一个从节点只能有一个主节点,数据的复制是单向的,只能从主节点复制到从节点。默认情况下,每一个Redis服务器都是一个主节点。

1.2、作用

  1. 数据备份:主从复制实现了数据的热备份,提高数据的安全性
  2. 故障恢复:当主节点发生问题不能提供服务时,从节点可以提供服务,保证Redis集群的可用性
  3. 负载均衡:在主从复制的基础上,可以实现读写分离,主节点负责操作,从节点负责操作,在写少读多的场景下,可以使用该模式大大提高Redis的并发量
  4. Redis高可用:主从复制是实现哨兵和集群的基础,主从复制是Redis高可用的基石

二、主从复制实现

2.1、集群准备

主从复制不会再同一台节点上,我们准备三台虚拟机,分别为test201,test202,test203,Redis端口分别为6379,6380和6381,我们将test201:6379当做主节点,余下两个为从节点。我们使用之前讲解的配置文件方式启动三台Redis节点。

2.2、实现方式

1、客户端命令:Redis服务启动后在客户端使用slaveof <masterip> <masterport>,该Redis服务会成为从节点。

127.0.0.1:6380> slaveof test201 6379

2、启动命令:使用redis-server命令时使用 --slaveof <masterip> <masterport>,该Redis会成为从节点

redis-server redis-6381.conf --slaveof test201 6379

3、配置文件:在从节点的配置文件中配置  slaveof <masterip> <masterport>

slaveof test201 6379

敲黑板:此三种方式实现Redis的主从复制,实现数据的热备份,即主节点新增,删除,修改key,从节点也会实时更新。

2.3、断开主从关系

那天想单飞或者另寻明主需要断开现在主从关系,在从节点上运行slaveof no one,实现断开,那么从节点上的数据不会清空,但是不再接收主节点的数据更新

2.4、查看主从复制关系

在客户端执行以下命令

info replication

test201:主节点,可以看出该节点是master,主节点,有一个从节点,ip为192.168.109.203,端口为6381

test203:从节点,可以看出该节点是slave,主节点是test201,端口为6379

三、数据同步

3.1、数据同步概述

在Redis复制的基础上从节点(slave)可以精确的复制主节点(master)上的数据,每当slave和master之间断开连接时,slavehi自动连接上master,并且无论这期间master发生了什么,slave都会尝试去同步精确的数据,称为master的精确副本。这个过程就是数据同步

Redis2.8之前,从节点向主节点发送sync命令请求数据同步,此时的同步时全量复制,Redis2.8之后从节点发送psync命令进行数据同步,此时根据主从节点当前状态不同,同步的结果可能是全量复制或者部分复制。

  1. 全量复制:用于初次复制或其他无法部分复制的情况,将主节点中的所有数据都发送给从节点,是一个很重的操作。
  2. 部分复制:用于网络中断等情况的复制,只将中断期间主节点的写操作发送给从节点,相比全量复制不比较高效,如果连接断开时间过长,导致主节点没有完整的保存断开时间内的数据,那么还是会使用全量复制。

3.2、同步机制

  • 当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave , :包括客户端的写入、key 的过期或被逐出等等。

  • 当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。

  • 当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。

Redis使用默认的异步复制,其特点是低延迟和高性能,是绝大多数 Redis 用例的自然复制模式。但是,slave会异步地确认其从master周期接收到的数据量。

3.3、全量复制

  1. slave判断无法进行部分复制,向master发送全量复制请求,或slave发送部分复制,master判断无法进行全量复制;
  2. master接收到全量复制指令之后,运行bgsave,在后台生成RDB文件,并生成一个缓冲区(复制缓冲区),记录从现在开始执行的所有写命令
  3. master的bgsave执行完毕之后,将RDB文件发送给slave,slave首先清除自身旧数据,然后载入接收的RDB文件,将数据库状态更新至master执行bgsave时的数据库状态;
  4. master将上文提到的复制缓冲区中所有写命令发送给slave,slave执行这些写命令,将数据库状态更新至master最新状态;
  5. 如果slave开启了AOF,则会触发bgrewriteaof操作,从而保证AOF文件更新到最新状态。

从上述可以看出全量复制是一个很重的操作:

  1. master通过bgsave使进行fork子进程进行RDB持久化,该过程非常消耗CPU、内存、硬盘IO;
  2. master通过网络将RDB文件发送给slave,对主/从节点的带宽都有很大损耗;
  3. slvae清空老数据,载入新的RDB文件,这个过程是阻塞的,无法响应客户端命令,slave执行bgrewrieaof也会到来损耗。

3.4、部分复制

因为全量复制损耗太大,Redis2.8版本开始提供部分复制,用于处理网络中断时的数据同步。部分复制实现主要依赖于三个重要的概念:复制偏移量,复制积压缓冲区,服务器运行ID

3.4.1、复制偏移量

主节点和从节点分别维护一个复制偏移量(offset),代表的是master向slave传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。例如,如果主节点的offset是1000,而从节点的offset是500,那么部分复制就需要将offset为501-1000的数据传递给从节点。而offset为501-1000的数据存储的位置,就是下面要介绍的复制积压缓冲区

3.4.2、复制积压缓冲区

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

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

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

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

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

3.4.3、服务器运行ID(runid)

每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;

runid用来唯一识别一个Redis节点。通过info Server命令,可以查看节点的runid

主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:

  • 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
  • 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制

3.5、psync命令执行流程

1、首先从节点根据当前状态,决定如何调用psync命令:

  • 如果从节点之前未执行过slaveof或最近执行了slaveof no one,则从节点发送命令为psync ? -1,向主节点请求全量复制;
  • 如果从节点之前执行了slaveof,则发送命令为psync <runid> <offset>,其中runid为上次复制的主节点的runid,offset为上次复制截止时从节点保存的复制偏移量。

2、主节点根据收到的psync命令,及当前服务器状态,决定执行全量复制还是部分复制:

  • 如果主节点版本低于Redis2.8,则返回-ERR回复,此时从节点重新发送sync命令执行全量复制;
  • 如果主节点版本够新,且runid与从节点发送的runid相同,且从节点发送的offset之后的数据在复制积压缓冲区中都存在,则回复+CONTINUE,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;
  • 如果主节点版本够新,但是runid与从节点发送的runid不同,或从节点发送的offset之后的数据已不在复制积压缓冲区中(在队列中被挤出了),则回复+FULLRESYNC <runid> <offset>,表示要进行全量复制,其中runid表示主节点当前的runid,offset表示主节点当前的offset,从节点保存这两个值,以备使用。

3.6、命令传播阶段

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

3.6.1、主->从:PING

每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让从节点进行超时判断。

PING发送的频率由 repl-ping-slave-period 参数控制,单位是秒,默认值是10s。

3.6.2、从->主:REPLCONF ACK

在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,频率是每秒1次;命令格式为:REPLCONF ACK {offset},其中offset指从节点保存的复制偏移量。

REPLCONF ACK命令的作用包括:

(1)实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1。

(2)检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。注意,offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。

(3)辅助保证从节点的数量和延迟:Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值。

四、应用中的问题

4.1、读写分离

4.1.1、读写分离概述

在主从复制基础上实现的读写分离,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);在读负载较大的应用场景下,可以大大提高Redis服务器的并发量。下面介绍在使用Redis读写分离时,需要注意的问题

4.1.2、复制数据延迟问题

前面已经讲到,由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。如果应用对数据不一致的接受程度程度较低,可能的优化措施包括:优化主从节点之间的网络环境(如在同机房部署);监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;使用集群同时扩展写负载和读负载等。

在命令传播阶段以外的其他情况下,从节点的数据不一致可能更加严重,例如连接在数据同步阶段,或从节点失去与主节点的连接时等。从节点的slave-serve-stale-data参数便与此有关:它控制这种情况下从节点的表现;如果为yes(默认值),则从节点仍能够响应客户端的命令,如果为no,则从节点只能响应info、slaveof等少数命令。该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为no。

4.1.3、数据过期问题

在单机版Redis中,存在两种删除策略:

  • 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
  • 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。

在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。

Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。

4.1.4、故障切换问题

在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低

4.1.5、总结

在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入

4.2、配置不一致

  1. 主从节点配置不一致,例如主从节点的maxmemory不一致可能造成丢失数据

  2. 数据结构优化参数(hash-max-ziplist-entries)造成内存不一致等问题

我们可以通过统一的配置方式去安装部署redis服务。

4.3、规避全量复制

4.3.1、第一次全量复制

第一次不可避免,我们可以使用使用小主节点,低峰时进行复制,小主节点表示数据分片不要太大,也就是我们的maxmemory不要设置的过大,低峰意为我们可以在夜间用户量少时做全量复制。

4.3.2、节点运行ID不匹配

主节点重启之后runid会发生变化,在Redis4.0之后可以使用故障转移的方式处理,使用集群或者哨兵模式

4.3.3、复制缓冲区不足

前面曾提到过,在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,该缓冲区存放的数据包括了以下几个时间段内主节点执行的写命令:bgsave生成RDB文件、RDB文件由主节点发往从节点、从节点清空老数据并载入RDB文件中的数据。当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;这种情况可能引起全量复制->复制缓冲区溢出导致连接中断->重连->全量复制->复制缓冲区溢出导致连接中断……的循环。

复制缓冲区的大小由client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默认值为client-output-buffer-limit slave 256MB 64MB 60,其含义是:如果buffer大于256MB,或者连续60s大于64MB,则主节点会断开与该从节点的连接。该参数是可以通过config set命令动态配置的(即不重启Redis也可以生效)。

需要注意的是,复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点分别分配复制缓冲区;而复制积压缓冲区则是一个主节点只有一个,无论它有多少个从节点

4.4、复制超时问题

主从节点复制超时是导致复制中断的最重要的原因之一。

4.4.1、超时判断意义

在复制连接建立过程中及之后,主从节点都有机制判断连接是否超时,其意义在于:

1、如果主节点判断连接超时,其会释放相应从节点的连接,从而释放各种资源,否则无效的从节点仍会占用主节点的各种资源(输出缓冲区、带宽、连接等);此外连接超时的判断可以让主节点更准确的知道当前有效从节点的个数,有助于保证数据安全(配合前面讲到的min-slaves-to-write等参数)。

2、如果从节点判断连接超时,则可以及时重新建立连接,避免与主节点数据长期的不一致。

4.4.2、判断机制

主从复制超时判断的核心,在于repl-timeout参数,该参数规定了超时时间的阈值(默认60s),对于主节点和从节点同时有效;主从节点触发超时的条件分别如下:

1、主节点:每秒1次调用复制定时函数replicationCron(),在其中判断当前时间距离上次收到各个从节点REPLCONF ACK的时间,是否超过了repl-timeout值,如果超过了则释放相应从节点的连接。

2、从节点:从节点对超时的判断同样是在复制定时函数中判断,基本逻辑是:

  • 如果当前处于连接建立阶段,且距离上次收到主节点的信息的时间已超过repl-timeout,则释放与主节点的连接;
  • 如果当前处于数据同步阶段,且收到主节点的RDB文件的时间超时,则停止数据同步,释放连接;
  • 如果当前处于命令传播阶段,且距离上次收到主节点的PING命令或数据的时间已超过repl-timeout值,则释放与主节点的连接。

4.4.3、需要注意的坑

1、数据同步阶段:在主从节点进行全量复制bgsave时,主节点需要首先fork子进程将当前数据保存到RDB文件中,然后再将RDB文件通过网络传输到从节点。如果RDB文件过大,主节点在fork子进程+保存RDB文件时耗时过多,可能会导致从节点长时间收不到数据而触发超时;此时从节点会重连主节点,然后再次全量复制,再次超时,再次重连……这是个悲伤的循环。为了避免这种情况的发生,除了注意Redis单机数据量不要过大,另一方面就是适当增大repl-timeout值,具体的大小可以根据bgsave耗时来调整。

2、命令传播阶段:如前所述,在该阶段主节点会向从节点发送PING命令,频率由repl-ping-slave-period控制;该参数应明显小于repl-timeout值(后者至少是前者的几倍)。否则,如果两个参数相等或接近,网络抖动导致个别PING命令丢失,此时恰巧主节点也没有向从节点发送数据,则从节点很容易判断超时。

3、慢查询导致的阻塞:如果主节点或从节点执行了一些慢查询(如keys *或者对大数据的hgetall等),导致服务器阻塞;阻塞期间无法响应复制连接中对方节点的请求,可能导致复制超时。

4.5、复制相关的配置

4.5.1、与主从节点都有关的配置

首先介绍最特殊的配置,它决定了该节点是主节点还是从节点:

1、slaveof <masterip> <masterport>:Redis启动时起作用;作用是建立复制关系,开启了该配置的Redis服务器在启动后成为从节点。该注释默认注释掉,即Redis服务器默认都是主节点。

2、 repl-timeout 60:与各个阶段主从节点连接超时判断有关,见前面的介绍。

4.5.2、主节点相关配置

1、repl-diskless-sync no:作用于全量复制阶段,控制主节点是否使用diskless复制(无盘复制)。所谓diskless复制,是指在全量复制时,主节点不再先把数据写入RDB文件,而是直接写入slave的socket中,整个过程中不涉及硬盘;diskless复制在磁盘IO很慢而网速很快时更有优势。需要注意的是,截至Redis3.0,diskless复制处于实验阶段,默认是关闭的。

2、repl-diskless-sync-delay 5:该配置作用于全量复制阶段,当主节点使用diskless复制时,该配置决定主节点向从节点发送之前停顿的时间,单位是秒;只有当diskless复制打开时有效,默认5s。之所以设置停顿时间,是基于以下两个考虑:(1)向slave的socket的传输一旦开始,新连接的slave只能等待当前数据传输结束,才能开始新的数据传输 (2)多个从节点有较大的概率在短时间内建立主从复制。

3、client-output-buffer-limit slave 256MB 64MB 60:与全量复制阶段主节点的缓冲区大小有关,见前面的介绍。

4、repl-disable-tcp-nodelay no:与命令传播阶段的延迟有关,见前面的介绍。

5、masterauth <master-password>:与连接建立阶段的身份验证有关,见前面的介绍。

6、repl-ping-slave-period 10:与命令传播阶段主从节点的超时判断有关,见前面的介绍。

7、repl-backlog-size 1mb:复制积压缓冲区的大小,见前面的介绍。

8、repl-backlog-ttl 3600:当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点重新连进来时,可以进行全量复制;默认3600s。如果设置为0,则永远不会释放复制积压缓冲区。

9、min-slaves-to-write 3与min-slaves-max-lag 10:规定了主节点的最小从节点数目,及对应的最大延迟,见前面的介绍。

4.3.3、从节点相关配置

1、slave-serve-stale-data yes:与从节点数据陈旧时是否响应客户端命令有关,见前面的介绍。

2、slave-read-only yes:从节点是否只读;默认是只读的。由于从节点开启写操作容易导致主从节点的数据不一致,因此该配置尽量不要修改。

五、总结

1、主从复制的作用:了解主从复制是为了解决什么样的问题,即数据冗余、故障恢复、读负载均衡等。

2、主从复制的操作:即slaveof命令。

3、主从复制的原理:主从复制包括了连接建立阶段、数据同步阶段、命令传播阶段;其中数据同步阶段,有全量复制和部分复制两种数据同步方式;命令传播阶段,主从节点之间有PING和REPLCONF ACK命令互相进行心跳检测。

4、应用中的问题:包括读写分离的问题(数据不一致问题、数据过期问题、故障切换问题等)、复制超时问题、复制中断问题等,然后总结了主从复制相关的配置,其中repl-timeout、client-output-buffer-limit slave等对解决Redis主从复制中出现的问题可能会有帮助。

主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注!

写在最后:

各位的支持和认可(点赞)是我最大的动力,请不要轻轻的来,用力留下你的足迹!

本篇文章有任何错误希望不吝指出,不胜感激!我们下篇再见!

求知并无捷径,如果有,那就是放弃这个幼稚的想法,静下心来多读书、总结

发布了23 篇原创文章 · 获赞 49 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_36386908/article/details/103149697
今日推荐