Redis-06-复制

配置

建立复制

复制的数据流是单向的,只能从主节点复制到从节点。配置复制的方式有以下三种:

  1. 在配置文件中加入 slaveof {masterHost} {masterPort} 随 Redis 启动生效
  2. redis-server 启动命令后加入 --slaveof {masterHost} {masterPort} 生效
  3. 直接使用命令:slaveof {masterHost} {masterPort} 生效

slaveof 本身是异步命令,执行 slaveof 命令时,节点只保存主节点信息后返回,后续复制流程在节点内部异步执行,主从节点复制成功建立后,可以使用 info replication 命令查看复制相关状态。

断开复制

在从节点执行 slaveof no one 来断开与主节点复制关系,断开复制主要流程:

  1. 断开与主节点复制关系
  2. 从节点晋升为主节点

通过执行 slaveof {newMasterHost} {newMasterPort} 命令可以实现切主操作,切主操作流程如下:

  1. 断开与旧主节点复制关系
  2. 与新主节点建立复制关系
  3. 删除从节点当前所有数据
  4. 对新主节点进行复制操作

安全性

主节点可设置 requirepass 参数进行密码验证,这时所有的客户端访问必须使用 auth 命令实行校验,因此需要配置从节点的 masterauth 参数与主节点密码保持一致,这样从节点才可以正确地连接到主节点并发起复制流程。

只读

默认情况下,从节点使用 slaveof-read-only = yes 配置为只读模式。

传输延迟

Redis 提供了 repl-disable-tcp-nodelay 参数用于控制是否关闭 TCP_NODELAY,默认关闭。

  • 当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗
  • 当开启时,主节点会合并较小的 TCP 数据包从而节省带宽,但增大了主从之间的延迟

拓扑结构

Redis 的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从和树状主从结构。

  1. 一主一从结构
    一主一从结构是最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持。当应用写命令并发量较高且需要持久化时,可以只在从节点上开启 AOF,这样既保证数据安全性同时也避免了持久化对主节点的性能干扰。
  2. 一主多从结构
    一主多从结构(又称为星形拓扑结构)使得应用端可以利用多个从节点实现读写分离。对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。对于写并发量较高的场景,多个从节点会导致主节点写命令的多次发送从而过度消耗网络带宽,同时也加重了主节点的负载影响服务稳定性。
  3. 树状主从结构
    树状主从结构(又称为树状拓扑结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层,可以有效降低主节点负载和需要传送给从节点的数据量。当主节点需要挂载多个从节点时为了避免对主节点的性能干扰,可以采用树状主从结构降低主节点压力。

原理

复制过程

  1. 保存主节点信息
  2. 主从建立 socket 连接:从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接
  3. 发送 ping 命令
  4. 权限验证
  5. 同步数据集
  6. 命令持续复制

数据同步

Redis 使用 psync 命令完成主从数据同步,同步过程分为全量复制和部分复制。

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

  1. 主从节点复制偏移量
    • 主节点(master)在处理完写入命令后,会把命令的字节长度做累加记录,统计信息在 info relication 中的 master_repl_offset 指标中
    • 从节点(slave)每秒钟上报自身的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量
    • 从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量。统计信息在 info relication 中的 slave_repl_offset 指标中
  2. 主节点复制积压缓冲区
    • 复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为 1 MB,本质上是先进先出的定长队列
    • 当主节点有连接的从节点(slave)时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区
  3. 主节点运行 ID
    • 每个 Redis 节点启动后都会动态分配一个40位的十六进制字符串作为运行 ID
    • 如果只使用 ip+port 的方式识别主节点,那么主节点重启变更了整体数据集(如替换 RDB/AOF 文件),从节点再基于偏移量复制数据将是不安全的,因此当运行 ID 变化后从节点将做全量复制
    • 可以使用 debug reload 命令重新加载 RDB 并保持运行 ID 不变,从而有效避免不必要的全量复制
    • 注意:debug reload 命令会阻塞当前 Redis 节点主线程,阻塞期间会生成本地 RDB 快照并清空数据之后再加载 RDB 文件。因此对于大数据量的主节点和无法容忍阻塞的应用场景,谨慎使用。

从节点使用 psync 命令完成部分复制和全量复制功能,命令格式 psync {runId} {offset}psync 命令运行流程:

  1. 从节点(slave)发送 psync 命令给主节点,参数 runId 是当前从节点保存的主节点运行ID,如果没有则默认值为 ?,参数 offset 是当前从节点保存的复制偏移量,如果是第一次参与复制则默认值为 -1。
  2. 主节点(master)根据 psync 参数和自身数据情况决定响应结果:
    • 如果回复 +FULLRESYNC {runId} {offset},那么从节点将触发全量复制流程
    • 如果回复 +CONTINUE,从节点将触发部分复制流程
    • 如果回复 +ERR,说明主节点版本低于 Redis2.8,无法识别 psync 命令,从节点将发送旧版的 sync 命令触发全量复制流程。

全量复制

  1. 从节点发送 psync 命令进行数据同步,如果是第一次进行复制,从节点没有复制偏移量和主节点的运行 ID,所以发送 psync ? -1
  2. 主节点根据 psync ? -1 解析出当前为全量复制,回复 +FULLRESYNC 响应
  3. 从节点接收主节点的响应数据保存运行 ID 和偏移量 offset
  4. 主节点执行 bgsave 保存 RDB 文件到本地
  5. 主节点发送 RDB 文件给从节点,从节点把接收的 RDB 文件保存在本地并直接作为从节点的数据文件
    • 针对数据量较大的节点,建议调大 repl-timeout 参数防止出现全量同步数据超时
    • 关于无盘复制:为了降低主节点磁盘开销,Redis 支持无盘复制,生成的 RDB 文件不保存到硬盘而是直接通过网络发送给从节点,通过 repldiskless-sync 参数控制,默认关闭。无盘复制适用于主节点所在机器磁盘性能较差但网络带宽较充裕的场景。
  6. 对于从节点开始接收 RDB 快照到接收完成期间,主节点仍然响应读写命令,因此主节点会把这期间写命令数据保存在复制客户端缓冲区内,当从节点加载完 RDB 文件后,主节点再把缓冲区内的数据发送给从节点,保证主从之间数据一致性
  7. 从节点接收完主节点传送来的全部数据后会清空自身旧数据
  8. 从节点清空数据后开始加载 RDB 文件
  9. 从节点成功加载完 RDB 后,如果当前节点开启了 AOF 持久化功能,它会立刻做 bgrewriteaof 操作,为了保证全量复制后 AOF 持久化文件立刻可用

部分复制

使用 psync {runId} {offset}命令实现。当从节点(slave)正在复制主节点(master)时,如果出现网络闪断或者命令丢失等异常情况时,从节点会向主节点要求补发丢失的命令数据,如果主节点的复制积压缓冲区内存在这部分数据则直接发送给从节点,这样就可以保持主从节点复制的一致性。

心跳检测

主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令,主从心跳判断机制:

  1. 主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信,通过 client list 命令查看复制相关客户端信息,主节点的连接状态为 flags = M,从节点连接状态为 flags = S。
  2. 主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态。可通过参数 repl-ping-slave-period 控制发送频率。
  3. 从节点在主线程中每隔 1 秒发送 replconf ack {offset} 命令,给主节点上报自身当前的复制偏移量。replconf 命令主要作用如下:
    • 实时监测主从节点网络状态
    • 上报自身复制偏移量,检查复制数据是否丢失,如果从节点数据丢失,再从主节点的复制缓冲区中拉取丢失数据
    • 实现保证从节点的数量和延迟性功能,通过 min-slaves-to-writeminslaves-max-lag 参数配置定义

异步复制

写命令的发送过程是异步完成,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。

开发与运维中的问题

读写分离

对于读占比较高的场景,可以通过把一部分读流量分摊到从节点(slave)来减轻主节点(master)压力,同时需要注意永远只对主节点执行写操作。

当使用从节点响应读请求时,业务端可能会遇到如下问题:

  • 数据延迟
  • 读到过期数据
  • 从节点故障

主从配置不一致

规避全量复制

全量复制的场景:

  1. 第一次建立复制

    • 无法规避,当对数据量较大且流量较高的主节点添加从节点时,建议在低峰时进行操作,或者尽量规避使用大数据量的 Redis 节点。
  2. 节点运行 ID 不匹配

    • 起因:如果主节点因故障重启,那么它的运行 ID 会改变,从节点发现主节点运行 ID 不匹配时,会认为自己复制的是一个新的主节点从而进行全量复制。
    • 方法:对于这种情况应该从架构上规避,比如提供故障转移功能。当主节点发生故障后,手动提升从节点为主节点或者采用支持自动故障转移的哨兵或集群方案。
  3. 复制积压缓冲区不足

    • 起因:当主从节点网络中断后,从节点再次连上主节点时会发送 psync {offset} {runId} 命令请求部分复制,如果请求的偏移量不在主节点的积压缓冲区内,则无法提供给从节点数据,因此部分复制会退化为全量复制。
    • 方法:针对这种情况需要根据网络中断时长,写命令数据量分析出合理的积压缓冲区大小。

规避复制风暴

复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。

  1. 单主节点复制风暴

    单主节点复制风暴一般发生在主节点挂载多个从节点的场景,解决方案有:

    • 可以减少主节点(master)挂载从节点(slave)的数量
    • 采用树状复制结构,加入中间层从节点用来保护主节点
  2. 单机器复制风暴
    当一台机器(machine)上同时部署多个主节点(master)时,如果这台机器出现故障或网络长时间中断,当它重启恢复后,会有大量从节点(slave)针对这台机器的主节点进行全量复制,会造成当前机器网络带宽耗尽。解决方案有:

    • 应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点
    • 当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密集的全量复制