目录

  • 一、复制介绍
  • 二、复制配置
    • 3.2.1 直接断开
    • 3.2.2 切换到其他master
    • 2.1.1 命令
    • 2.1.2 演示
    • 2.1 建立复制
    • 2.2 断开复制
  • 三、拓扑结构
  • 四、复制过程
  • 五、数据同步原理
    • 5.1.1 主从复制偏移量
    • 5.1.2 主节点复制积压缓冲区
    • 5.1.3 主节点运行id
    • 5.1 psync命令需要的组件
    • 5.2 psync命令
    • 5.3 全量复制
    • 5.4 部分复制
  • 六、主从心跳
    • 6.1 流程
    • 6.2 repl -timeout参数
  • 七、全量复制场景
  • 七、一些配置和命令


一、复制介绍

主从复制,是把一台redis服务器上数据复制到其他服务器的机制,其中前者被称为主节点(master),后者被称为从节点(slave)。

主从复制的主要主要作用:

  1. 数据冗余:数据热备,多机备份。
  2. 故障恢复:当主节点出现问题时,可以让从节点提供服务,是一种功能的冗余。
  3. 负载均衡:可以让主节点写,从节点多,可以把压力分配到多个从节点,从而实现负载均衡。
  4. 高可用基石:主从复制是实现哨兵和集群的基础。

默认情况下,每个redis服务器都是master节点,每一个master可以有多个slave节点,但是一个salve节点只能有一个master节点。

二、复制配置

2.1 建立复制

2.1.1 命令

  1. 在配置文件中加入:slaveof {masterHost} {masterPort}
  2. 在redis-server启动命令后加入 -- slaveof {masterHost} {masterPort}
  3. 直接使用命令(在客户端执行): slaveof {masterHost} {masterPort}

2.1.2 演示

准备节点

默认6380端口作为master节点,再启动一个6380节点作为salve节点。

复制一个配置文件,命名为redis6380.conf

配置新的端口号

redis-复制_redis

两个redis实例已经启动。

redis-复制_redis_02

执行复制命令

启动两个实例

redis-复制_redis_03

redis-复制_redis_04

执行slaveof命令

redis-复制_redis_05

查看效果

master节点写入,读取

redis-复制_redis_06

slave节点读取数据

redis-复制_redis_07

至此,复制搭建成功,数据已经成功从master复制到了slave,并且通过salve读取成功。

2.2 断开复制

3.2.1 直接断开

直接使用slaveof no one命令即可断开和master的复制关系

redis-复制_redis_08

断开复制关系后的数据

  1. 原有已经复制的数据会保留
  2. master后续写入的数据将不再同步

可以看到原有的数据保留了

redis-复制_redis_09

断开后在原有master写入

redis-复制_redis_10

数据将不再同步到6380

redis-复制_redis_11

3.2.2 切换到其他master

可以通过切换到其他master的方式断开和当前master的绑定。但是和slave no one不同的是,切换新的master后,从原有master复制过来的数据会被清空。

redis-复制_redis_12

三、拓扑结构

一对一,一对多,树状结构。

四、复制过程

redis-复制_redis_13

4.1保存主节点

执行slaveof后从节点只是保存了主节点的地址信息变直接返回,复制流程还没有正式开始。

4.2 主从建立socket连接

从节点内部通过每秒运行的定时任务来处理相关逻辑,当定时任务发现存在新的主节点后,会尝试和主节点建立网络连接。

从节点会创建一个socket去连接主节点,后续数据同步都是基于这个socket进行。

如果从节点无法建立连接,定时任务会无限重试到连接成功或者复制被取消为止。

4.3 发送ping命令

连接建立成功后从节点会向主节点发送ping请求进行首次通信,主要有如下目的:

  1. 检测之前建立的socket是否可用。
  2. 检测主节点当前是否可接受处理命令

发送ping命令后如果从节点没有收到pong回复或者超时(比如网络超时,或者主节点阻塞无法处理等),从节点会断开复制,下次定时任务发起后重新连接。

4.4 权限验证

如果主节点设置了requirepass参数,则需要密码验证,从节点必须配置masterauth参数保证与主节点相同的密码才能通过验证,如果验证失败,从节点会断开复制,下次定时任务发起后重新连接。

4.5 数据同步

主从复制连接建立成功后,便开始数据同步,属于数据的初始化,主节点会把持有的所有数据发送给从节点,主题是实现方式是从节点给主节点发送psync命令(2.8之前是sync命令)。这块是耗时最长的步骤,分为全量同步和部分同步。

4.6 命令持续同步

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

五、数据同步原理

主从建立连接成功后,从节点会向主节点发送psync命令来完成数据同步,同步过程分为:全量复制和部分复制。

  1. 全量复制:一般用于初次复制场景,主节点一次性把全部数据发给从节点,是一个比较重的操作
  2. 部分复制:用于处理在主从复制中因网络闪断等原因造成的数据丢失的场景,当主从再次连接后,如果主节点完整保存了中断期间的数据,主节点会补发丢失的数据给从节点,补发的数据远远小于全量数据,部分复制有效避免了全量复制的过高开销。

5.1 psync命令需要的组件

psync命令运行需要以下组件的支持:

  1. 主从复制偏移量
  2. 主节点复制挤压缓冲区
  3. 主节点运行id

5.1.1 主从复制偏移量

master节点处理完写入命令后,会把命令的字节长度做累加记录。

redis-复制_redis_14

从节点再接收到主节点发送的命令后,也会累加自身的偏移量。

redis-复制_redis_15

通过对比master的偏移量和slave的偏移量来看slave和master的数据差异大小。

5.1.2 主节点复制积压缓冲区

复制缓冲区是保存在主节点上的一个固定长度队列,默认大小为1MB,当有slave时候回创建缓冲区,这时主节点响应写命令时,不但会把命令发送给从节点,还会写入复制挤压缓冲区。

redis-复制_redis_16

挤压缓冲区是一个先进先出的队列,如果超过容量,之前的数据会被覆盖。大小是可以配置的。挤压缓冲区主要为了部分复制做准备。可以通过info replication来查看:

repl_backlog_active:1 //开启复制缓冲区
repl_backlog_size:1048576 //缓冲区最大长度
repl_backlog_first_byte_offset:4505 //起始变异量,计算当前缓冲区可用范围
repl_backlog_histlen:5460 //已保存数据的有效长度

5.1.3 主节点运行id

每个redis节点(主从)启动后都会生成一个40位的16进制的字符串作为运行id,用于唯一识别一个redis节点。从节点会保存主节点的运行id用于识别自己正在复制的是哪一个主节点。redis重启后id会改变。

redis-复制_redis_17

初次复制时,从节点会保存主节点的runid。

5.2 psync命令

从节点通过给主节点发送psync命令实现部分复制或者全量复制。命令格式为:

psync {runid} {offset}
  1. runid:所辅助主节点的runid,
  2. offset:当前从节点的数据偏移量

第一次复制时没有offset和主节点的runid,会发送psync -1命令

redis-复制_redis_18

5.3 全量复制

redis-复制_redis_19

  1. 首次复制发送 psync ?  -1
  2. master 继续请求发现是全量复制,恢复fullresync {runid} {offset}
  3. slave保存主节点响应的runid和 offset
  4. 主节点执行bgsave,并且把从选择开始的命令同时写入一个缓冲区(复制缓冲区)
  5. 主节点执行完bgsave后把最终生成的rdb发给salve。
  6. salve收到主节点发送过来的rdb后开始清空自身数据
  7. salve把主节点的rdb载入自己的rdb,此时salve的数据更新至主节点执行bgsave时候的状态。
  8. 主节点将复制缓冲区的命令发送给salve
  9. slave执行接收到的复制缓冲区的命令,至此salve的数据更新至主节点的最新状态
  10. 如果slave开启了aof,则会立即做bgrewriteaof,确保全量复制后aof持久化文件立即可用。

5.4 部分复制

部分复制时redis针对全量复制开销过高做出的一种优化措施,使用psync  {runid}{offset}命令实现。

当主从复制的过程中,如果出现网络闪断或者命令丢失等异常情况,从节点会要求主节点补发数据,如果此时主节点的复制积压缓冲区内存中刚好存在这部分数据(就是断网这段时间没有同步到从节点的数据),则直接发给从节点,最终保持了和从节点的一直,也避免了大规模的全量复制。

redis-复制_redis_20

  1. 如果主从节点之间网络出现中断,如果超过repl-timeout时间,主节点会认为从节点故障并中断复制连接。
  2. 主从断开后,主节点依然在响应命令,这个时候新的命令无法同步到从节点,主从出现了不一致。上面也解释了主节点会默认将命令写入到复制积压缓冲区,默认为1M,超出后会覆盖。
  3. 网络恢复了,从节点再次连接主节点,连接建立成功。
  4. 从节点保存了主节点的runid和自身的复制偏移量(offset),通过psync  {runid} {offset}命令和主节点进行交互
  5. 主节点如果发现满足部分复制的条件(后面详细解释这个条件),则返回continue给从节点
  6. 主节点根据偏移量把复制积压缓冲区的数据发给从节点,最终保证主从复制进入正常。

主节点判断判断满足部分复制的条件

  1. runid必须和自身一致
  2. 从节点发过来的偏移量之后的数据都在自身的复制积压缓冲区内,这个很好理解,比如从节点发过来的offset是10(代表10以后的数据都没有同步),但是缓冲区的offset是15(说明15之前的的数据已经不在缓冲区了),这个时候就没办法进行部分复制

如果不满足部分复制条件,则主节点会返回fullsync给从节点,从节点会开启全量复制。

由此可见复制积压缓冲区的大小比较重要,如果太小,会被覆盖,最终导致主从网络恢复后无法进行部分复制,这个值得大小应该要基于网络的中断时间,已经主节点的qps和命令的大小来进行计算,然后进行合理的设置。

六、主从心跳

6.1 流程

主从心跳检测示意图

redis-复制_redis_21

  1. master会周期性的ping slave,周期时间通过repl-ping-replica-period参数来控制,默认是10秒

  2. slave每隔1秒回向master发送replconf ack  {offset}命令:

  • 如果master开启了这两个参数,那么如果可用的从节点小于min-replicas-to-write或者延迟大于min-replicas-max-lag,master会拒绝数据写入。示意图如下。

    redis-复制_redis_22

  • 实时监测主从节点的网络状态

  • 上报自身的数据复制偏移量,如果主节点发现从节点有数据缺失,主节点会从自身的复制积压缓冲区中拉取数据发给从节点。

  • 实现从节点的数量和延迟性功能,通过min-replicas-to-write(最小可用的从节点个数)和min-replicas-max-lag(允许的最小延迟秒,一般为0,或者1)参数定义。

6.2 repl -timeout参数

redis.conf有个repl-timeout参数:

  1. slave角度,如果在repl-timeout时间内没有收到传输的rdb snapshot数据,

  2. slave角度,如果在repl-timeout没有收到master发送的数据包或者ping。

  3. master角度,如果在repl-timeout时间没有收到REPCONF ACK确认信息。

当redis检测到repl-timeout超时(默认值60s),将会关闭主从之间的连接,redis slave会重新建立主从连接的请求。这个值一定要大于repl-ping-replica-period参数

为了降低主从延迟,一般建议把redis的主从节点部署在相同的机房。

七、全量复制场景

全量复制非常重,应该尽量避免,下面是一些会导致全量复制的操作。

  1. 第一次建立复制,无法避免,建议低峰时候进行
  2. runid不匹配,从节点会保存主节点的runid,如果主节点重启,则主节点的runid会改变,发现和从节点保存的runid不一致时,会进行全量复制,应该避免重启,比如可以采用debug reload命令,或者采用故障转移功能,当主节点发生故障后,可以将从节点提升为主节点,或者采用哨兵或者集群方案
  3. 复制积压缓冲区不足(repl-backlog-size),这个缓冲区默认大小为1M,当超过1m后覆盖,主从中断再次连接后如果从节点的offset在复制积压缓冲区找不到,则会导致全量复制,这个缓冲区的大小要基于网络状况,命令大小,以及qps进行计算配置。
七、一些配置和命令
  1. salve-read-only=yes。 从节点只读,如果从节点修改,会造成主从数据不一致
  2. repl-disable-tcp-nodelay 是否关闭tcp_nodelay,默认为no,建议配置为yes。这个是服务器tcp的一个功能,tcp nagle算法
  3. debug reload,不会导致runid改变,但是会情况内存数据,再次从rdb加载。
  4. 。。。