首先我先说说主从复制是什么?为了减轻redis的压力,使用了读写分离的形式,也就是主节点写,从节点读,这样就要求主从节点数据必须一致,所以就有了主从复制。主从复制就是,将一个主节点上的数据全部复制到其他的从节点上,以保持主从节点的数据一致。

具体怎么实现的呢?首先,其他节点需要先向主节点发送psync命令来发起开始同步。接着,主节点通过发送来的psync来判断是进行全局同步还是增量复制。假如是第一次进行同步,或者是之前的连接失效,就会进行全量复制。全量复制完毕后,主从节点之间会维护一个长链接,在此期间会将主节点的操作进行增量复制到从节点,维护主从节点一致性。

为什么需要主从复制?

  1. 降低数据冗余: 主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 提供故障恢复: 当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;
  3. 支持负载均衡: 主从复制配合读写分离,主节点写服务,从节点读服务,写数据连主节点,读数据连从节点,可分担服务器负载。写少读多场景下,多个从节点分担读负载,提高 Redis 服务器并发量。
  4. 高可用: 主从复制是 Redis 高可用的基础,也是哨兵和集群实施的基础。

主从库之间采用的是 读写分离 的方式:

  1. 读操作: 主库、从库可以接收
  2. 写操作: 仅主库可执行,然后主库将写操作同步给从库

两种同步复制方式

  1. 全量复制: 第一次同步时
  2. 增量复制: 只会把主从库网络断联期间主库收到的命令同步交给从库

全量复制

基于 TCP 长连接避免频繁建立 TCP 链接和断开带来的性能开销,分为三阶段如图:

  • 假设主机 Master 在 6379 端口,从机 Slave 在 6380端口; 基于本机 (方便理解后续扩展内容: 搭建一主多从)
  • 其中 repl buffer 是 replication buffer 简写

第一阶段

slave 向 master 发送 psync指令,表示进行数据同步,

psync包含两个参数,分别是 主服务器的 runID 和 复制进度 offset

  • runID: 标识每一个 Redis 服务器
  • offset: 第一次同步表示 -1

FULLRESYNC 响应命令意图是全量复制,主服务器将所有数据同步给从服务器。

第二阶段

Master 执行 bgsave 指令来生成 RDB 文件,然后把文件发送给从服务器

Slave 接收到 Master 传输的 RDB 之后,会先清空当前的数据,然后载入 RDB 文件

Master 通过 bgsave指令会产生一个子进程来进行 RDB 文件生成的工作,是异步工作的。

这期间写操作命令未记录到刚生成的 RDB 文件中,主从服务器数据不一致。

为了保证一致性,Master 在三个时间间隙将写操作命令写入 replication buffer 缓冲区。

  1. Master 生成 RDB 文件期间
  2. Master 发送 RDB 文件给 Slave 服务器期间
  3. Slave 加载 RDB 文件期间

第三阶段

Master 生成的 RDB 文件发送完,Slave 收到后,丢弃旧数据,载入 RDB 数据到内存,完成载入后回复确认消息给 Master。

然后 Master 需要将这期间记载的在 replication buffer 缓冲区内的写操作记录也发送给 Slave, 保证一致性

问题:

但是第一次全量复制之后如果遇到网络问题无法进行命令传播,Slave 就无法和 Master 保持同步了,导致不一致性 而如果恢复连接之后使用全量复制保证一致性的话开销太大,因为有一部分数据已经一致。有什么办法吗?

增量复制

图示:

  • repl backlog 表示 repl_backlog_buffer
  • repl buffer 表示 replication buffer

过程

  1. 服务器恢复网络后,会向主服务器发送 psync 命令(offset 非 -1)。
  2. 主服务器收到后,用 CONTINUE 响应命令告知从服务器采用增量复制同步数据。
  3. 接着,主服务器将断线期间执行的写命令发给从服务器,从服务器执行这些命令。

其中:

  • repl_backlog_buffer 是一个 ⌈环形⌋ 缓冲区,用于主从服务器断开连接后,从中找到差异的数据
  • replication offset标记同步进度,Master 用于记录自己写的未知,Slave 记录自己读的未知

repl_backlog_buffer 缓存的写入时机

  • Master 在将写指令发送给 Slave 的同时,将其写入到 repl_backlog_buffer 缓冲区内

如何辨别应该向 Slave 执行全量复制还是增量复制

Slave 通过 psync指令将自己的 slave_repl_offset 发送给主服务器;服务器依据 master_repl_offset 与 slave_repl_offset 的差距决定对从服务器的同步操作。

  1. 若从服务器所需数据在 repl_backlog_buffer 缓冲区,主服务器采用增量同步;
  2. 若不在,主服务器采用全量同步。

由于 repl_backlog_buffer 缓冲区的默认大小是 1M。 由于时一个环形缓冲区,如果写满,会发生 覆盖问题 。网络恢复时,若服务器想读的数据被覆盖,主服务器采用全量同步,此方式比增量同步性能损耗大很多。

因此为了避免频繁的全量同步,应该调大 repl_backlog_buffer

推荐大小

second * write_size_per_second
  • second 为从服务器断线后重新连接上主服务器所需的平均时间 (单位 s)
  • write_size_per_second 则是主服务器平均每秒产生的写命令数据量大小。

支持基于 redis.conf 配置项修改 repl_backlog_buffer 缓冲区大小

(扩展) - 如何搭建一主多从?

创建一个测试目录

修改 redis.conf

  • 确认其中的配置项为 daemonize = yes

并且仅开启 RDB 备份持久化策略

  • 关闭混合持久化策略 aof-user-rdb-preamble 设置为 no
  • 关闭 AOF 持久化策略 appendonly 设置为 no

 

分别创建三个配置文件

示例: redis6379.conf、redis6380.conf、redis6381.conf

并且分别添加如下配置

include [放置配置文件的目录路径]/redis.conf
pidfile /var/run/redis_6379.pid
port [基于文件后缀,比如 6379 端口]
dbfilename dump[端口].rdb

参考配置

分别启动三台 redis 服务器

连接查看相关信息

  • 指令 info replication 用于打印主从复制的相关信息

配置主从关系

将 6380 和 6381 配置成为 Slave ; 而 6379 作为主机 Master 借助如下指令,配置某个实例的从服务器

  • 指令 slaveof <master_ip> <master_port>
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
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:4
master_sync_in_progress:0
slave_read_repl_offset:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:6635dfe974961f6624785748ea2ed035bc670b99
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

 

查看主机信息

配置了两个从节点

  • 一个为端口 6380 的服务
  • 一个为端口 6381 的服务

测试: 主机支持读写,但是从机仅支持读

主机 Master 支持读写

从机 Slave 仅支持读

尝试写报错: READONLY You can’t write against a read only replice.

(扩展) - 测试主从复制 之 全量复制

流程简述:

  1. Slave 启动成功会连接到 master 后会发送一个 sync 指令
  2. Master 接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据的指令,在后台进程执行完毕之后,Master 将传送整个数据文件到 slave, 完成一次完全同步
  3. Slave 服务在接收到数据库文件数据之后,将其存盘并且加载到内存之中,即 全量复制

测试:Slave down 后重启仍然获取到 Master 最新数据

Master 添加数据

  • 推荐这个好用的 Redis 可视化工具 Another Redis Desktop Manager (๑•̀ㅂ•́)و✧

查看从库

  • 已经自动同步

停止从库

主库继续添加新的数据

当前还存活: 6379 主(Master), 6380 从(Slave)

重启从库 6381 从库启动之后,默认仍然是 master

再次将其绑定 Master, 设置为从库

获取从库内的所有 key

  • 进行全量复制,6381从库仍然可以得到最新的数据

主库 Master 内此时的最新数据

测试:Master 意外 down 后重启自动恢复 Master 地位

现象: 当 master 恢复之后,从服务器 Slave 仍然指向原来的主服务器

在 6379 主 Master 服务器内执行

SHUTDOWN

确认 Master 已经 Down

此时从服务器

重启端口为 6379 的 Master

查看当前各个服务节点是否启动

[root@xxx]# redis-server redis6379.conf
[root@xxx]# ps -ef | grep redis
root      372752       1  0 06:30 ?        00:00:10 redis-server *:6380
root      374411       1  0 06:50 ?        00:00:03 redis-server *:6381
root      374416  366219  0 06:50 pts/1    00:00:00 redis-cli -p 6381
root      375125       1  5 06:58 ?        00:00:00 redis-server *:6379
root      375135  374917  0 06:58 pts/2    00:00:00 grep --color=auto redis

6379主服务器启动之后仍然是 master

查询从服务器 Slave 信息 可以监控到主服务器已经启动 提问:从节点能不能再挂从节点?形成级联复制?

回答:可以的。从节点可以配置成另一个从节点的主节点,形成链式结构。这样做的好处是减轻主节点的复制压力。假设有 10 个从节点都直接连主节点,主节点要维护 10 个 replication buffer,要发 10 份数据。如果用级联结构,主节点只连 2 个从节点,这 2 个再各自连 4 个,主节点的压力就小多了。缺点是链路越长延迟越高,最底层的从节点数据延迟会叠加。

提问:主从复制的时候从节点还能对外提供读服务吗?

回答:默认可以,但读到的可能是旧数据。如果要求严格,可以配置 slave-serve-stale-data no,这样从节点在同步完成前拒绝所有读请求,只返回 SYNC with master in progress 错误。一般不建议这么配,会影响可用性。

提问:主节点挂了从节点会自动升级成主节点吗?

回答:不会。单纯的主从复制没有自动故障转移能力。从节点发现主节点挂了,就一直等着,不会自己升级。要实现自动切换得上哨兵或者 Redis Cluster。哨兵会监控主从节点状态,主节点挂了自动选一个从节点提升成新主节点。

提问:repl_backlog_buffer 是所有从节点共用一个,那怎么知道每个从节点同步到哪了?

回答:每个从节点会记录自己的复制偏移量 offset,主节点也记录自己当前写到哪个 offset 了。从节点重连时把自己的 offset 告诉主节点,主节点拿这个 offset 去 repl_backlog_buffer 里查。buffer 里每条数据都有对应的 offset 范围,能查到就增量同步,查不到说明数据被覆盖了就全量同步。