Redis 主从复制

1. 主从赋值

从节点slave实例能精确得复制主节点master实例的内容。每次当slave和master断开时,slave会自动重连到master上,并且无论这期间master发生了什么,slave都会尝试将自己成为master的精确副本。

主从赋值的机制:

  • master和slave正常连接时:master会发送一连串的命令流来保持对slave的更新,以便于将自身的数据集复制给slave。包括客户端的写入、key的过期与丢弃等等。
  • master和slave断开,重连后:slave重新连接上master后,会尝试进行部分重同步。slave只会尝试获取在断开连接期间内丢失的命令流。
  • 无法进行重同步:如果无法进行重同步,那么slave就请求进行全量重同步。master需要创建所有数据的快照,将之发送给slave,之后在数据集更改时,持续发送命令流到slave。

redis使用默认的异步复制,具有低延迟和高性能的特点,也是大多数的自然复制模式。slave会异步确认master周期性发送的命令流(数据流)。

客户端可以使用WAIT命令请求同步复制某些特定的数据。但是WAIT命令只能确保在slave中存在指定数据的已确认副本。虽然slave已确认了副本存在,但是slave在持久化副本的时候,还是会受到slave自己的持久化策略的影响,最终仍然会因持久化策略,导致同步期间的数据丢失。

Redis 主从复制

2. 主从复制的特点

对于主从复制来说,一个master可能存在多个slave,随着slave的数量的增加,慢慢的master给slave发送数据流,就成为了master性能下降的原因之一了。

所以在redis中,有以下功能来保证主从复制:

  • 一个master可以有很多的slave
  • redis使用异步复制,slave和master之间可以异步确认处理的数据
  • ==slave可以接受其他slave的连接。==除了多个slave可以连接到同一个master之外,slave之间也可以以层叠结构连接到其他slave。在redis4.0开始,所有的slave会收到完全一样的赋值流。
  • 主从复制在master是非阻塞的,master可以在一个或多个slave进行初次同步或者是部分同步时,还可以继续处理查询请求。
  • 主从复制在slave大部分也是非阻塞的。当slave进行初次同步时,他可以使用旧数据集处理查询请求(需要在redis.conf中配置),否则slave会返回error给客户端。初次同步之后,旧的数据集会被删除,同时加载新的数据集,如果数据集很大,这个操作会造成slave短暂的卡顿,在卡顿期间阻塞。在redis4.0开始,可以配置slave删除旧的数据集在其他线程完成。但是加载新的数据集依然是主线程完成,依然会有小小的卡顿(非常不明显)。
  • 主从复制可以用在可伸缩性上,比如读多写少的场景,也可以作为备份机器使用(热备份,当主节点出现问题了,备份节点可以不做任何修改,在极其短的时间内成为主节点,实现主节点的操作)
  • 主从复制可以转移持久化,提高性能。比如slaveA是热备份机器,slaveB是持久化机器。slaveB从master或者slaveA得到完整的数据集,然后写入硬盘,因为master不需要做硬盘持久化,所以在一定的程度上可以提升性能。

3. 主从复制的安全

在第二小节中,我们谈到,利用主从赋值,可以将master的持久化转移给slave。要实现slave代替master进行持久化,就需要master关闭持久化。

但是master关闭持久化会存在非常严重的隐患:

假设在主从赋值的过程中,master重启了,因为master没有配置持久化,而且也没有从slave获取持久化文件。那么重启后的master就是一个没有任何数据的redis实例。slave重新和master连接后,并不知道master发生了重启,于是进行部分同步,所以slave也将全部的数据删除,成为了空数据的redis实例。将空数据持久化到硬盘,同时删除旧的持久化文件,此时就造成了数据全部丢失的严重问题。

如何解决:

关闭自动重启机制,当master因各种原因宕机,不应该立即重启,而是要从slave拿到持久化文件,然后在启动,让重新后的master根据持久化文件进行重建数据。

4. 主从复制的原理

每一个 master 都有一个 replication ID:这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master 也持有一个偏移量,master 将自己产生的复制流发送给 slave 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新 slave 的状态。复制偏移量即使在没有一个 slave 连接到 master 时,也会自增,所以基本上每一对给定的

Replication ID, offset

都会标识一个 master 数据集的确切版本。

当 slave 连接到 master 时,它们使用 PSYNC命令来发送它们记录的旧的 master replication ID 和它们至今为止处理的偏移量。通过这种方式, master 能够仅发送 slave 所需的增量部分。但是如果 master 的缓冲区中没有足够的命令积压缓冲记录,或者如果 slave 引用了不再知道的历史记录(replication ID),则会转而进行一个全量重同步:在这种情况下, slave 会得到一个完整的数据集副本,从头开始。

下面是一个全量同步的工作细节:

master 开启一个后台保存进程,以便于生产一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master 将数据集文件传输给 slave, slave将之保存在磁盘上,然后加载文件到内存。再然后 master 会发送所有缓冲的命令发给 slave。这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同。

你可以用 telnet 自己进行尝试。在服务器正在做一些工作的同时连接到 Redis 端口并发出 SYNC 命令。你将会看到一个批量传输,并且之后每一个 master 接收到的命令都将在 telnet 回话中被重新发出。事实上 SYNC 是一个旧协议,在新的 Redis 实例中已经不再被使用,但是其仍然向后兼容:但它不允许部分重同步,所以现在 PSYNC 被用来替代SYNC

之前说过,当主从之间的连接因为一些原因崩溃之后, slave 能够自动重连。如果 master 收到了多个 slave 要求同步的请求,它会执行一个单独的后台保存,以便于为多个 slave 服务。

5. 无需磁盘参与的复制

正常情况下,一个全量重同步要求在磁盘上创建一个 RDB 文件,然后将它从磁盘加载进内存,然后 slave以此进行数据同步。

如果磁盘性能很低的话,这对 master 是一个压力很大的操作。Redis 2.8.18 是第一个支持无磁盘复制的版本。在此设置中,子进程直接发送 RDB 文件给 slave,无需使用磁盘作为中间储存介质。

Redis 主从复制

6. 配置主从复制

6.1 配置文件

slaveof host port

将这个命令加入redis.conf文件中即可使得redis实例成为从节点,host的redis实例是主节点。

Redis 主从复制

6.2 配置命令

在从节点的redis实例中使用slaveof命令,也会将当前节点设置为目标节点的slave,不过重启后会被redis.conf的配置重置。

小例子:

Redis 主从复制

将slave设置为master的从节点。

Redis 主从复制

master增加数据

Redis 主从复制

然后在slave查看,是否进行复制

Redis 主从复制

发现没有进行主从复制,是时间没到吗?那么就使用WAIT命令强制进行主从复制了

Redis 主从复制

一直没有进行主从复制。是打开方式不对吗,还是哪里出错了?

仔细回忆,slave的配置文件中有这样一句:

Redis 主从复制

应该是这个配置导致的,我们注释掉这个配置,然后重启

重新设置slave

Redis 主从复制

但是我又失败了。。。嗯嗯

Redis 主从复制

配置中的protectted-mode是和bind配合使用的吧

在试

Redis 主从复制

因为我在win 10系统上启动的redis-server,而且前面就将redis-server注册到操作系统的服务中心了

Redis 主从复制

使用redis-server --service-insttall config-path即可将redis-server注册。

使用redis-server --service-stop 停止

使用redis-server --service-start启动

使用redis-server --service-resart重启

重启了

Redis 主从复制

然后发现还是没有同步,看下日志

Redis 主从复制

嗯,应该是版本问题造成的:

Redis 主从复制

那么如果我本地在启动一个呢

Redis 主从复制

我又在本地启动了一个,端口是6301.

6301将是master,6300是slave

Redis 主从复制

成功实现。我们在6301中是没有做任何操作的,但是6300已经拥有了6301中的数据了。

7. slave只读

自从 Redis 2.6 之后, slave 支持只读模式且默认开启。redis.conf 文件中的 slave-read-only 变量控制这个行为,且可以在运行时使用 CONFIG SET 来随时开启或者关闭。

Redis 主从复制

只读模式下的 slave 将会拒绝所有写入命令,因此实践中不可能由于某种出错而将数据写入 slave 。

由于slave不会将数据传播到与该实例相连的slave上,slave总是接收与顶层redis实例发送的数据流。

A
B
C

对于C来说,C只会接收和A完全相同的数据流。

如果数据流在B中发生了修改,那么C是不接收的。

8. slave 验证

redis实例是可以配置密码的安全验证的。

所以,如果slave需要连接到master,需要通过验证才行。

需要在slave的配置文件中配置

masterauth <password>

或者在命令行中使用

config set masterautn <password>进行配置

9. slave 数量控制

从Redis 2.8开始,只有当至少有 N 个 slave 连接到 master 时,才有可能配置 Redis master 接受写查询。

但是,由于 Redis 使用异步复制,因此无法确保 slave 是否实际接收到给定的写命令,因此总会有一个数据丢失窗口。

以下是该特性的工作原理:

  • slave 每秒钟都会 ping master,确认已处理的复制流的数量。
  • master 会记得上一次从每个 slave 都收到 ping 的时间。
  • 用户可以配置一个最小的 slave 数量,使得它滞后 <= 最大秒数。

如果至少有 N 个 slave ,并且滞后小于 M 秒,则写入将被接受。

你可能认为这是一个尽力而为的数据安全机制,对于给定的写入来说,不能保证一致性,但至少数据丢失的时间窗限制在给定的秒数内。一般来说,绑定的数据丢失比不绑定的更好。

如果条件不满足,master 将会回复一个 error 并且写入将不被接受。

这个特性有两个配置参数:

  • min-slaves-to-write <slave 数量>
  • min-slaves-max-lag <秒数>

Redis 主从复制

10. 主从复制处理过期数据

Redis 的过期机制可以限制 key 的生存时间。此功能取决于 Redis 实例计算时间的能力,但是,即使使用 Lua 脚本更改了这些 key,slaves 也能正确地复制具有过期时间的 key。

为了实现这样的功能,Redis 不能依靠主从使用同步时钟,因为这是一个无法解决的并且会导致 race condition 和数据集不一致的问题,所以 Redis 使用三种主要的技术使过期的 key 的复制能够正确工作:

  • slave 不会让 key 过期,而是等待 master 让 key 过期。当一个 master 让一个 key 到期(或由于 LRU 算法将之驱逐)时,它会合成一个 DEL 命令并传输到所有的 slave。
  • 但是,由于这是 master 驱动的 key 过期行为,master 无法及时提供 DEL 命令,所以有时候 slave 的内存中仍然可能存在在逻辑上已经过期的 key 。为了处理这个问题,slave 使用它的逻辑时钟以报告只有在不违反数据集的一致性的读取操作(从主机的新命令到达)中才存在 key。用这种方法,slave 避免报告逻辑过期的 key 仍然存在。在实际应用中,使用 slave 程序进行缩放的 HTML 碎片缓存,将避免返回已经比期望的时间更早的数据项。
  • 在Lua脚本执行期间,不执行任何 key 过期操作。当一个Lua脚本运行时,从概念上讲,master 中的时间是被冻结的,这样脚本运行的时候,一个给定的键要么存在要么不存在。这可以防止 key 在脚本中间过期,保证将相同的脚本发送到 slave ,从而在二者的数据集中产生相同的效果。

一旦一个 slave 被提升为一个 master ,它将开始独立地过期 key,而不需要任何旧 master 的帮助。

如果主从复制中主节点和从节点处理过期数据不及时,会造成主节点与读节点读取数据不一致的问题。

11. 主从复制信息查询

有两个 Redis 命令可以提供有关主从实例当前复制参数的很多信息。一个是INFO。如果使用复制参数像 INFO replication 调用该命令,,则只显示与复制相关的信息。另一个更加 友好 的命令是 ROLE,它提供 master 和 slave 的复制状态以及它们的复制偏移量,连接的 slaves 列表等等。