redis系列(七)—主从复制

redis系列(七)—主从复制

前言

大家好,牧码心今天给大家推荐一篇redis系列(七)—主从复制的文章,在实际工作中有很多应用场景,希望对你有所帮助。内容如下:

  • 主从复制概述
  • 主从复制配置
  • 数据同步
  • 心跳机制
  • 常见问题

概述

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

  • 作用
    • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
    • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
    • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
    • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础;

配置主从复制

建立复制
主从复制是由从节点发起,主节点不需要配置。配置方式有以下三种:
(1)配置文件
在从服务器的配置文件中加入:slaveof
(2)启动命令
redis-server启动命令后加入 --slaveof
(3)客户端命令
Redis服务器启动后,直接通过客户端执行命令:slaveof ,则该Redis实例成为从节点。
综合来看,这三种方式是等效的,下面以客户端方式来演示实例

实例演示
为了方便演示,实例所使用的主从节点是在一台机器上的不同Redis实例,其中主节点监听6379端口,从节点监听6380端口;从节点监听的端口号可以在配置文件中修改。

  • 在从节点6380端口上执行slaveof
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
# 主节点写入数据
127.0.0.1:6379> set slave redis
OK
127.0.0.1:6379> get slave
"redis"
# 从节点获取数据
127.0.0.1:6380> get slave
"redis"

成功建立复制后,从节点日志会提示:
在这里插入图片描述
slaveof本身是异步命令, 执行slaveof命令时, 节点只保存主节点信息后返回, 后续复制流程在节点内部异步执行, 具体细节见之后工作原理小节。主从节点复制成功建立后, 可以使用info replication命令查看复制相关状态, 如下所示:

  • 主节点6379的状态信息:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=14357,lag=0
master_repl_offset:14357
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:14356
  • 从节点6380的状态信息
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:14371
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

注意:若主节点通过requirepass参数进行密码设置,这时从节点复制需要在从节点配置中加上masterauth参数与主节点密码保持一致, 这样从节点才可以正确地连接到主节点并发起复制流程。

断开复制
slaveof命令不但可以建立复制, 还可以在从节点执行slaveof no one来断开复制。从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。在从节点上执行slaveof no one 后,看输出日志所示;可以看出断开复制后,从节点又变回为主节点。
在这里插入图片描述

主从复制原理
在从节点执行slaveof命令后,主从复制过程便开始执行,我们先看下主从复制的流程图,如图所示:
主从复制建立流程图
从图中我们可以看出主从复制大致分为以下几个过程:

  • 保存主节点信息
    在从节点上执行slaveof后从节点只保存主节点的地址信息便直接返回, 这时建立复
    制流程还没有开始, 在从节点6380执行info replication可以看到如下信息:
    master_host:127.0.0.1
    master_port:6379
    master_link_status:down
    

从统计信息可以看出, 主节点的ip和port被保存下来, 但是主节点的连接状态(master_link_status) 是下线状态。

  • 建立socket连接
    从节点每秒通过定时任务函数维护连接,如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。如果连接成功,则
    从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。
    主节点:接收到从节点的socket连接后,就会创建相应的客户端状态,并将此从节点看做是连接到主节点的一个客户端。
    这个过程中,从节点打印日志如下:
    在这里插入图片描述
    如果连接失败,定时任务会无限重试直到连接成功或者执行slaveof no one取消复制。

    注意:关于连接失败, 可以在从节点执行info replication查看master_link_down_since_seconds指标, 它会记录与主节点连接失败的系统时间

  • 发送ping命令
    连接建立成功后从节点发送ping请求进行首次通信,这时可以:
    1.检测主从之间网络套接字是否可用;
    2.检测主节点当前是否可接受处理命令。
    如果发送ping命令后, 从节点没有收到主节点的pong回复或者超时, 比如网络超时或者主节点正在阻塞无法响应命令, 从节点会断开复制连接, 下次定时任务会发起重连。如图所示
    主从节点建立连接流程

  • 权限验证
    如果主节点设置了requirepass参数, 则需要密码验证(没有设置该选项,则不需要验证),从节点必须配置masterauth参数保证与主节点相同的密码才能通过验证; 如果验证失败复制将终止, 从节点重新发起复制流程。

  • 同步数据集
    主从完成连接建立通信后,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤 Redis在2.8版本以后采用新复制命令psync进行数据同步, 原来的sync命令依然支持,保证新旧版本的兼容性。新版同步划分两种情况:全量同步和部分同步,后面会详细说明。

  • 持续复制
    当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。但主节点会持续地把写命令发送给从节点,保证主从数据一致性。

数据同步

主从节点之间的连接建立以后,便可以开始进行数据同步、Redis在2.8及以上版本使用psync命令完成主从数据同步,同步过程分为:全量复制和部分复制。
全量复制:一般用于初次复制场景, Redis早期支持的复制功能只有全量复制,它会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。
部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失场景, 当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据, 可以有效避免全量复制的过高开销。

注意:部分复制是对老版复制的重大优化, 有效避免了不必要的全量复制操作。 因此当使用复制功能时, 尽量采用2.8以上版本的Redis。

下面分别说明下这2种复制的执行流程:

  • 全量复制
    全量复制是Redis最早支持的复制方式,也是主从第一次建立复制时必须经历的阶段。 触发全量复制的命令是sync和psync, 它们的对应版本如图:
    redis版本复制命令差异
    这里是以psync命令来说明全量复制的过程:
    1.从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行全量复制;具体判断过程需要在讲述了部分复制原理后再介绍。

    2.主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令;

    3.主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态;

    4.主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态;

    5。如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态;

    通过全量复制的过程可以看出,全量复制是非常重型的操作:

    (1)主节点通过bgsave命令fork子进程进行RDB持久化,该过程是非常消耗CPU、内存(页表复制)、硬盘IO的;

    (2)主节点通过网络将RDB文件发送给从节点,对主从节点的带宽都会带来很大的消耗;

    (3)从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行bgrewriteaof,也会带来额外的消耗;

    下面是执行全量复制时,主从节点打印的日志;可以看出日志内容与上述步骤是完全对应的。
    全量复制从节点日志

  • 部分复制
    由于全量复制在主节点数据量较大时效率太低,因此Redis2.8开始提供部分复制,用于处理网络中断时的数据同步。
    1)当主从节点之间网络出现中断时,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接;

    2)主从连接中断期间主节点依然响应命令,但因复制连接中断命令无法发送给从节点,不过主节点内部存在的复制积压缓冲区,依然可以保存最近一段时间的写命令数据,默认最大缓存1MB。

    3)当主从节点网络恢复后,从节点会再次连上主节点。

    4)当主从连接恢复后, 由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。 因此会把它们当作psync参数发送给主节点, 要求进行部分复制操作。

    5)主节点接到psync命令后首先核对参数runId是否与自身一致, 如果一致, 说明之前复制的是当前主节点; 之后根据参数offset在自身复制积压缓冲区查找, 如果偏移量之后的数据存在缓冲区中, 则对从节点发送+CONTINUE响应, 表示可以进行部分复制。

    6)主节点根据偏移量把复制积压缓冲区里的数据发送给从节点, 保证主从复制进入正常状态。

另外以下几个概念在主从复制是很重要的,我们分别说明下:

  • 主从节点各自复制的偏移量
    参与复制的主从节点都会维护自身复制偏移量。 主节点(master) 在处理完写入命令后, 会把命令的字节长度做累加记录, 统计信息在info relication中的master_repl_offset指标中。
    从节点(slave) 每秒钟上报自身的复制偏移量给主节点, 因此主节点也会保存从节点的复制偏移量, 统计指标如下:

    127.0.0.1:6379> info replication
    connected_slaves:1
    slave0:ip=127.0.0.1,port=6380,state=online,offset=1055214,lag=1
    

从节点在接收到主节点发送的命令后, 也会累加记录自身的偏移量。统计信息在info relication中的slave_repl_offset指标中:

  • 复制积压缓冲区
    复制积压缓冲区是保存在主节点上的一个固定长度的队列, 默认大小为1MB, 当主节点有连接的从节点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。
    由于缓冲区本质上是先进先出的定长队列, 所以能实现保存最近已复制数据的功能, 用于部分复制和复制命令丢失的数据补救。 复制缓冲区相关统计信息保存在主节点的info replication中:
127.0.0.1:6379> info replication
role:master
...
repl_backlog_active:1 // 开启复制缓冲区
repl_backlog_size:1048576 // 缓冲区最大长度
repl_backlog_first_byte_offset:7479 // 起始偏移量, 计算当前缓冲区可用范围
repl_backlog_histlen:1048576 // 已保存数据的有效长度。
  • 主节点运行ID
    每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。运行ID的主要作用是用来唯一识别Redis节点,比如从节点保存主节点的运行ID识别自己正在复制的是哪个主节点。 如果只使用ip+port的方式识别主节点,那么主节点重启变更了整体数据集(如替换RDB/AOF文件),从节点再基于偏移量复制数据将是不安全的, 因此当运行ID变化后从节点将做全量复制。可以运行info server命令查看当前节点的运行ID。
127.0.0.1:6379> info server
# Server
redis_version:3.2.9
process_id:26852
run_id:650032b3a7a7b48685033ddfd6f6a130ca7df97e

那如何在不改变运行ID的情况下重启呢?当需要调优一些内存相关配置, 例如: hash-max-ziplist-value等, 这些配置需要Redis重新加载才能优化已存在的数据, 这时可以使用debug reload命令重新加载RDB并保持运行ID不变, 从而有效避免不必要的全量复制。 命令
如下:

#redis-cli -p 6379 info server | grep run_id
run_id:650032b3a7a7b48685033ddfd6f6a130ca7df97e
# redis-cli debug reload
OK

debug reload命令会阻塞当前Redis节点主线程, 阻塞期间会生成本地RDB快照并清空数据之后再加载RDB文件。 因此对于大数据量的主节点和无法容忍阻塞的应用场景, 谨慎使用。

psync命令

在了解了复制偏移量、复制积压缓冲区、节点运行id之后,将介绍psync命令的参数和返回值,从而说明psync命令执行过程中,主从节点是如何确定使用全量复制还是部分复制的。
从节点使用psync 命令完成部分复制和全量复制,命令格式为:

psync{runId}{offset}
·runId: 从节点所复制主节点的运行id。
·offset: 当前从节点已复制的数据偏移量。
  • psync命令运行流程,如图:
    psync运行流程
    从上图可以看出:

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

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

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

  • 如果主节点版本低于Redis2.8,则返回-ERR回复,此时从节点重新发送sync命令执行全量复制;
  • 如果主节点版本够新,且runid一致,且从节点发送的offset之后的数据在复制积压缓冲区中都存在,则回复+CONTINUE,表示将进行部分复制;
  • 如果主节点版本够新,但runid不同,或从节点发送的offset之后的数据已不在复制积压缓冲区中,则回复+FULLRESYNC runid offset,表示要进行全量复制;

心跳机制

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。
心跳检测
从上图我们分析下心跳机制:
1)主从节点彼此都有心跳检测机制, 各自模拟成对方的客户端进行通信, 通过client list命令查看复制相关客户端信息, 主节点的连接状态为:flags=M, 从节点连接状态为:flags=S

2)主节点默认每隔10秒对从节点发送ping命令, 判断从节点的存活性和连接状态。 可通过参数repl-ping-slave-period控制发送频率。

3)从节点在主线程中每隔1秒发送replconf ack{offset}命令,给主节点上报自身当前的复制偏移量。 replconf命令主要作用如下:

  • 实时监测主从节点网络状态。
  • 上报自身复制偏移量, 检查复制数据是否丢失, 如果从节点数据丢失, 再从主节点的复制缓冲区中拉取丢失数据。
  • 实现保证从节点的数量和延迟性功能, 通过min-slaves-to-write、 min-slaves-max-lag参数配置定;

主节点根据replconf命令判断从节点超时时间, 体现在info replication统计中的lag信息中, lag表示与从节点最后一次通信延迟的秒数, 正常延迟应该在0和1之间。 如果超过repl-timeout配置的值(默认60秒) , 则判定从节点下线并断开复制客户端连接。 即使主节点判定从节点下线后, 如果从节点重新恢复, 心跳检测会继续进行。

常见问题

分析基于复制的应用场景。通过复制机制,数据集可以存在多个副本(从节点)。这些副本可以应用于读写分离、故障转移(failover)、实时备份等场景。 但是在实际应用复制功能
时,依然有一些坑:

读写分离
对于读占比较高的场景, 可以通过把一部分读流量分摊到从节点(slave) 来减轻主节点(master) 压力,同时需要注意永远只对主节点执行写操作,如图所示。
在这里插入图片描述当使用从节点响应请求读时,会存在以下业务问题:

  • 复制数据延迟;
  • 读取到过期数据;
  • 从节点发送故障;

参考

  • 《Redis开发与运维》
发布了91 篇原创文章 · 获赞 27 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/xhwwc110/article/details/105202536