一. 概述
使用和配置主从复制非常简单, 每次当 slave 和 master 之间的连接断开时, slave 会自动重连到 master 上, 并且无论这期间 master 发生了什么, slave 都将尝试让自身成为 master 的精确副本. 这个系统的运行依靠三个主要的机制:
(1) 当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新, 以便于将自身 (master) 数据集的改变复制给 slave . 包括客户端的写入, key 的过期或 master 库的其它改变动作.
(2) 在 Redis2.6 之前, 主从断开重连后, 会进行一次快照操作 (rdb) 然后将快照发送给从数据库, 即使断开期间只有几条命令被执行, 这就使得断开重联后的数据恢复过程效率很低. Redis2.8 之后, 当 master 和 slave 之间的连接断开之后(因为网络问题, 主库或从库检测到超时), slave 会重新连接上 master 并尝试进行 "部分重同步" 也叫增量复制(partial resynchronization), 用于获取在断开连接期间内丢失的命令流.
(3) 当无法进行部分重同步时, slave 会请求进行 "完整同步"(full resynchronization). 这会涉及到一个更复杂的过程, 例如 master 需要创建所有数据的快照, 将之发送给 slave , 之后在 master 数据集更改时持续发送命令流到 slave. 也就是 2.8 后有了全部同步和部分重同步两种模式, 而 2.6 之前只有全部同步模式.
二. 复制特点
Redis 使用默认的异步复制, 具有低延迟和高性能, 是绝大多数 Redis 使用的复制模式. 从库会异步的确认与主库定期接收到的数据量. 主库不会每次去等待从库处理完命令. 类似 MySQL 半同步复制功能可以通过 min-slaves 配置选项来辅助. 下面介绍 Redis 复制的一些特点:
(1) Redis 使用异步复制, slave 和 master 之间异步地确认处理的数据量.
(2) 一个 master 可以拥有多个 slave.
(3) slave 可以接受其他 slave 的连接. 除了多个 slave 可以连接到同一个 master 之外, slave 之间也可以像层叠状的结构 (cascading-like structure) 连接到其他 slave . 自 Redis 4.0 版本起所有的 sub-slave 将会从 master 收到完全一样的复制流.
(4) Redis 复制在 master 这边是非阻塞的. 这意味着 master 在一个或多个 slave 进行初次同步或者是部分重同步时, 可以继续处理查询请求.
(5) 复制在 slave 这边大部分也是非阻塞的. 当 slave 进行初次同步时, 它可以使用旧数据集处理查询请求, 假设你在 Redis.conf 中配置了让 Redis 这样做的话.
(6) 复制既可以被用在可伸缩性, 以便只读查询可以有多个 slave 进行(例如 O(N) 复杂度的慢操作可以被下放到 slave ), 或者仅用于数据安全.
(7) 可以使用复制来避免 master 将全部数据集写入磁盘造成的开销: 一种典型的技术是配置你的 master Redis.conf 以避免对磁盘进行持久化, 然后连接一个 slave , 其配置为不定期保存或是启用 AOF. 但是, 这个设置必须小心处理, 因为重新启动的 master 程序将从一个空数据集开始: 如果一个 slave 试图与它同步, 那么这个 slave 也会被清空.
(8) 在使用 Redis 复制功能时, 强烈建议在 master 和在 slave 中启用持久化. 假如: 持久化被关闭了, Redis 主节点重启后其数据集合为空. 而其它 slave 从节点会复制主节点数据, 在复制时会销毁自身之前的数据副本. 这样下来复制构架的所有节点数据都会被清空.
三 Redis 复制同步原理
3.1 全部同步的实现
当客户端向从服务器发送 slaveof 命令, 要求从服务器复制主服务器时, 从服务器首先需要执行同步操作, 将从服务器的数据库状态更新至主服务器当前所处的数据库状, 过程如下:
(1) 从服务器向主服务器发送 psync 命令.
(2) 收到 psync 命令的主服务器执行 BGSAVE 命令, 开启一个后台子进程生成一个 RDB 快照文件, 并同时使用一个缓冲区记录从现在执行的所有写命令.
(3) 当主服务器的 BGSAVE 命令执行完, 主服务器会将生成的 RDB 文件发送给从服务器, 从服务器接收并载入这个 RDB 文件, 然后加载文件到内存. 将自己的数据库状态更新至主服务器执行 BGSAVE 命令时的数据库状态.
(4) 主服务器将记录在缓冲区里面的所有写命令发送给从服务器, 从服务器执行这些写命令, 将自己的数据库状态更新至主服务器数据库当前所处状态. 这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同.
下面是使用 psync 命令进行全部同步的过程:
时间 | 主服务器 | 从服务器 |
T0 | 服务器启动 | 服务器启动 |
T1 | 执行 set k1 v1 |
|
T2 | 执行 set k2 v2 |
|
T4 | .. | 向主服务器发送 psync 命令 |
T5 | 接收到从服务器的 psync 命令,执行 bgsave 命令,创建 RDB 文件,并使用缓冲区记录接下来执行所有写命令. |
|
T6 | 执行 set k3 v3, 将这个命令记录在缓冲区中 |
|
T7 | 执行 set k4 v4, 将这个命令记录在缓冲区中 |
|
T8 | Bgsave 执行完成,向从服务器发送 RDB 文件 |
|
T9 |
| 接收载入 RDB 文件 |
T10 | 向从服务器发送缓冲区中保存的写命令 |
|
T11 |
| 接收缓冲区中的写命令,执行 |
T12 | 同步完成 | 同步完成 |
3.2 部分重同步的实现
在主从服务器断开后, 从服务器会尝试不断的发送 psync 命令, 使用部分重同步模式, 让主从服务器重新回到一致状态. 使用部分重同步主服务器只需要将从服务器缺少的写命令发送给从服务器, 从服务器执行就可以了. 功能由三个部份构成:
(1) 主服务器的复制偏移量 (replication offset) 和从服务器的复制偏移量.
(2)主服务器的复制积压缓冲区(replication backlog).
(3) 服务器的运行 ID (run ID) .
3.2.1 复制偏移量: 主服务器每次向从服务器传播 N 个字节的数据时, 就将自己的复制偏移量的值加上 N. 从服务器每次接收到主服务器传播来的 N 个字节的数据时, 就将自己的复制偏移量的值加上 N.
例如: 主服务器向从服务器传播长度为 33 个字节数据, 那么主服务器的复制偏移量将更新为 10086+33=10119, 而从服务器在接收到主服务器传播的数据之后, 也会将复制偏移量更新为 10119. 如果主从偏移量相同, 表示主从服务器处于一致状态, 反之则不一致.
3.2.2 复制积压缓冲区: 该缓冲区是由主服务器维护一个固定长度先进先出的队列, 默认大小为 1MB. 当主服务器进行命传播时, 它不仅会将写命令发送给所有从服务器, 还会将写命令入队列到复制积压缓冲区中. 因此主服务器的复制积压缓冲区中会保存着一部分最近传播的写命令, 并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量.
当从服务器重新连上主服务器时, 从服务器会通过 psync 命令将自己的复制偏移量 offset 发送给主服务器, 主服务器会根据这个复制偏移量来决定从服务器执行何种同步操作:
(1) 如果从服务器发送的 offest 偏移量之后的数据仍然存在于复制积压缓冲区中, 那么主服务器将对从服务器执行部分重同步操作.
(2) 如果从服务器发送的如果 offest 偏移量之后的数据已经不存在于复制积压缓冲区中, 那么主服器将对从服务器执行完整同步操作.
注意: 缓冲区默认大小为 1MB, 如果主服务器需要执行大量写命令, 又或者主从服务器断线后重连接所需的时间比较长, 那么这个大小也许并不合适. 查看积压缓冲区大小 (单位字节) 如下:
- 127.0.0.1:6379> config get repl-backlog-size
- 1) "repl-backlog-size"
- 2) "1048576"
3.2.3 服务器运行 ID: 每个 Redis 服务器, 不论主从都有自己的运行 ID. 运行 ID 在服务器启动时自动生成, 由 40 个随机的十六进制字符组成. 当从服务器对主服务器进行初次复制时, 主服务器会将自已运行的 ID 传送给从服务器, 从服务器会将这个运行 ID 保存起来. 当从服务器断开重连到一个主服务器时, 从服务器将向当前连接的主服务器发送之前保存的运行 ID
(1) 如果从服务器保存的运行 ID 和当前连接的主服务器的运行 ID 相同, 那么说明从服务器断开后重连的还是之前的主服务器, 主服务器将继续尝试执行部分重同步操作.
(2) 反之, 如果运行 ID 不相同, 那说明从服务器断开后重连的不是之前的主服务器, 主服务器将对从服务器执行完整同步操作.
下面使用 psync 命令来进行断线后, 重复制的过程:
时间 | 主服务器 | 从服务器 |
T0 | 主从服务器完成同步 | 主从服务器完成同步 |
T1 | 执行并传播 set k1 v1 | 执行主服务器传来的 set k1 v1 |
.. | .. | .. |
T10087 | 主从服务器连接断开 | 主从服务器连接断开 |
T10088 | 执行 set k10087 v10087 | 断线后,尝试重新连接主服务器 |
T10089 | 执行 set k10088 v10088 | 断线后,尝试重新连接主服务器 |
T10090 | 主从服务器重新连接 | 主从服务器重新连接 |
T10091 |
| 向主服务器发送 psync 命令 |
T10092 | 向从服务器返回 + continue 回复,表示执行部分重同步 |
|
T10093 |
| 接收 + continue 回复,表示执行部分重同步 |
T10094 | 主从服务器再次完成同步 | 主从服务器再次完成同步 |
四. psync 命令的实现
4.1 PSYNC 命令的调用方法有两种:
(1) 如果从服务器以前没有复制过任何主服务器, 那么从服务器在开始一次新的复制时, 将向主服务器发送 psync ? -1 命令, 主动请求主服务器进行完整同步.
(2) 相反, 如果从服务器已经复制过某个主服务器, 那么从服务器在开始一次新的复制时, 将向主服务器发送 psync runid offset 命令. 通过两个参数来判断应该对服务器执行哪种同步操作.
4.2 根据情况, 主服务器接收到 psync 命令返回以下三种回复的其中一种:
(1) 如果主服务器返回 +fullresync runid offset 回复, 那么表示主服务器将与从服务器执行 "完整同步" 操作.
(2) 如果主服务器返回 + continue 回复, 那么表示主服务器将与从服务器执行 "部分重同步" 操作.
(3) 如果主服务器返回 - err 回复, 那么表示主服务器的版本低于 Redis 2.8, 它识别不了 psync 命令, 从服务器将向主服务器发送 sync 命令, 并与主服务器执行完整同步操作.
下图是 psync 命令执行完整同步和部分重同步时可能遇到的情况:
来源: https://www.cnblogs.com/MrHSR/p/10100490.html