【Redis】重学主从模式

779 阅读6分钟

这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战

一、概述

即使用了 AOFRDB单机 Redis 也依然存在服务不可用的问题。 例如:一个 Redis 实例宕机了,之后在恢复期间,是无法服务新来的数据。

Redis 具有高可靠性,意味着:

  1. 数据尽量少丢失AOFRDB 保证了。

  2. 服务尽量少中断:增加副本冗余量。

    即将一份数据同时保存在多个实例上。 即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。

单机的 redis 几乎不太可能 QPS 超过 10w。

要提高并发,一般的方案是 读写分离

多实例保存同一份数据,就会存在多实例(副本)之间的数据如何保持一致性问题?

Redis 提供了主从模式,以保证数据副本的一致,主从库之间采用读写分离方式:

  1. 读操作:主库、从库读可以接收。(主要是从库)
  2. 写操作:首先到主库执行,之后主库同步给从库 2021-11-2716-32-37.png

那么问题来了:主从库同步是如何完成的呢?

主从库同步

先来了解下,Redis 主从库同步三种模式:

  • 全量复制
  • 基于长连接的命令传播
  • 增量复制

启动多个 Redis 实例,可以通过 replicaofRedis 5.0 之前使用 slaveof)命令来形成主库和从库关系。

举个栗子:

  • 实例1 (ip: 172.16.19.3
  • 实例2 (ip172.16.19.5

在实例2上执行 replicaof 命令,使实例2变为实例1的从库:

replicaof  172.16.19.3  6379

主从库数据同步的三个阶段,如下:

  • 第一阶段:建立连接,协商同步
  • 第二阶段:主库同步数据给从库
  • 第三阶段:主库发送新写命令给从库 2021-11-2716-46-21.png

第一阶段:建立连接,协商同步

第一阶段:是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。

  1. 从库发送命令 psync 从库会发送 psync 命令(表示需要进行数据同步)给主库,主库会根据命令的参数来进行复制。 psync 命令包含了主库的 runID 和 复制进度 offset
  • runID:是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。

    当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。

  • offset:此时设为 -1,表示第一次复制。

  1. 主库接收 psync 命令后:会用 FULLRESYNC 命令进行响应
  • 主库会执行 bgsave 命令,生成对应的 RDB 文件。

FULLRESYNC 响应表示第一次复制采用的全量复制。 参数:主库 runID 和 主库目前的复制进度 offset


第二阶段:主库同步数据给从库

  1. 从库: 从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。

因为是第一次全量复制,所以从库里之前的数据都是脏数据。

  1. 主库: 主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。

全量复制只是当前的时刻主库的全量,之后会有增量同步,所以需要记录入每个从库的进度。


第三阶段:主库发送新写命令给从库

这个阶段是增量同步。

当主从库完成了全量复制,它们之间就会一直维护一个网络连接:避免频繁建立连接。

  1. 主库: 同步之后的写命令给从库。
  • 发送写命令
  • 同时会在 replication buffer 中修改对应操作
  1. 从库: 从库只需接收命令,在本地内存中执行。

二、问题

(1)集群过大:主从级联模式

当集群过大后,影响主库主要操作有:

  1. fork 操作:如果从库数量过多,且都要和主库进行全量复制,就会导致主库忙于 fork 子进程进行 RDB 文件。

fork 操作会阻塞主线程处理正常请求,从而导致主库响应应用程序的请求速度变慢。

  1. 传输 RDB 文件:占用主库的网络带宽

为了分担主库压力:可以采用 主 - 从 - 从 模式。

说白了,把从库假装成主库,在其下在进行关联子从库。

# 命令操作类似:
replicatof 所选从库IP 6379

2021-11-2809-23-15.png


(2)主从库断连会怎样?断连重连又是如何?

1)主从断连:可能会造成数据不一致

当主从库断连后:

  1. 主库会把断连期间收到的写操作命令,写入 replication buffer存储写命令
  2. 同时也会把这些操作命令写入 repl_backlog_buffer 缓冲区:存储记录

repl_backlog_buffer 是一个环形缓存区:

  • 主库会记录自己写道的位置
  • 从库则会记录自己已经读到的位置

repl_backlog_buffer 是一个环形缓冲区,当写满之后,会覆盖之前写入的操作。

如果从库的读取速度慢,就可能导致从库还未读取,主库已经开始覆盖写入了。这就会造成主从库间的数据不一致。

为避免这一情况,需要根据实际环境计算出合理的缓存区大小:

  • 缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。

  • 在实际应用中,需要考虑到可能存在一些突发的请求压力,通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2。

举个栗子:

  • 主库每秒写入 2000 个操作
  • 每个操作大小为 2KB
  • 网络每秒能传输 1000 个操作

这时候就有 1000 个操作需要缓冲起来,就至少需要 2MB 的缓冲区(repl_backlog_size)。

为了应对可能的突发压力,可以把 repl_backlog_size 设置为 4MB


2)断连后又重连

大致流程如下: 2021-11-2809-56-00.png

断开重连,需要知道主从双方需要知道对方情况:

  • 从库:我的状况是这样的
  • 主库:我看你需要这样的

所以,主从库的连接恢复之后:

  • 从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库
  • 主库会判断自己的 master_repl_offsetslave_repl_offset 之间的差距
    • master_repl_offset:主库写入的位置,偏移量
    • slave_repl_offset:从库已复制的偏移量

在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于 slave_repl_offset

此时,主库只用把 master_repl_offsetslave_repl_offset 之间的命令操作同步给从库就行。