# Redis 复制

## 旧版复制功能 <a href="#jiu-ban-fu-zhi-gong-neng" id="jiu-ban-fu-zhi-gong-neng"></a>

> Redis 2.8 版本以前实现方式：`SYNC` 命令

Redis 的复制功能分为同步和命令传播两个操作：

* `同步（sync）` - 用于将从服务器的数据库状态更新至主服务器当前的数据库状态。
* `命令传播（command propagate）` - 当主服务器的数据库状态被修改，导致主从数据库状态不一致时，让主从服务器的数据库重新回到一致状态。

### 同步 <a href="#tong-bu" id="tong-bu"></a>

### 命令传播 <a href="#ming-ling-chuan-bo" id="ming-ling-chuan-bo"></a>

同步操作完成后，主从数据库的数据库状态将达到一致。每当主服务器执行客户端发送的写命令时，主从数据库状态不再一致。需要将写命令发送给从服务器执行，使得二者的数据库状态重新达到一致。

### 旧版复制功能的缺陷 <a href="#jiu-ban-fu-zhi-gong-neng-de-que-xian" id="jiu-ban-fu-zhi-gong-neng-de-que-xian"></a>

从服务器对主服务器的复制存在两种情况：

* 初次复制
* 断线后重复制

对于初次复制，旧版复制功能可用很好完成任务；但是对于断线后重复制，由于每次任然需要生成 RDB 并传输，效率很低。

> 💡 注意：
>
> **SYNC 命令是一个非常耗费资源的操作。**
>
> 1. 主服务器执行 `BGSAVE` 命令生成 RDB 文件，这个操作会耗费主服务器大量的 CPU、内存和磁盘 I/O 资源。
> 2. 主服务器传输 RDB 文件给从服务器，这个操作会耗费主从服务器大量的网络资源，并对主服务器响应时延产生影响。
> 3. 从服务器载入 RDB 文件期间，会阻塞其他命令请求。

## 新版复制功能 <a href="#xin-ban-fu-zhi-gong-neng" id="xin-ban-fu-zhi-gong-neng"></a>

> Redis 2.8 版本以后的新实现方式：使用 `PSYNC` 命令替代 `SYNC` 命令。

`PSYNC` 命令具有完整重同步和部分重同步两种模式：

* `完整重同步（full resychronization）` - 用于初次复制。执行步骤与 `SYNC` 命令基本一致。
* `部分重同步（partial resychronization）` - 用于断线后重复制。如果条件允许，主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器，从服务器只需接收并执行这些写命令，即可将主从服务器的数据库状态保持一致。

### 部分重同步的实现 <a href="#bu-fen-zhong-tong-bu-de-shi-xian" id="bu-fen-zhong-tong-bu-de-shi-xian"></a>

部分重同步功能实现由三个部分构成：

* 复制偏移量（replication offset）
* 复制积压缓冲区（replication backlog）
* 服务器的运行 ID

它的工作原理是这样：**主服务器端为复制流维护一个内存缓冲区（in-memory backlog）。主从服务器都维护一个复制偏移量（replication offset）和 master run id ，当连接断开时，从服务器会重新连接上主服务器，然后请求继续复制，假如主从服务器的两个 master run id 相同，并且指定的偏移量在内存缓冲区中还有效，复制就会从上次中断的点开始继续**。如果其中一个条件不满足，就会进行完全重新同步（在 2.8 版本之前就是直接进行完全重新同步）。

![](https://139036132-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lx53lMutrsyPUks5pJf%2F-LyhXzxcDhGJR9iLcVIa%2F-LyhYBSZ9IQ8hRuI4gLN%2Fimage.png?alt=media\&token=1dc444af-3de7-4f51-8ad7-3483cfc8f40a)

#### 复制偏移量 <a href="#fu-zhi-pian-yi-liang" id="fu-zhi-pian-yi-liang"></a>

主服务器和从服务器会分别维护一个复制偏移量。

如果主从服务器的复制偏移量相同，则说明二者的数据库状态一致；反之，则说明二者的数据库状态不一致。

#### 复制积压缓冲区 <a href="#fu-zhi-ji-ya-huan-chong-qu" id="fu-zhi-ji-ya-huan-chong-qu"></a>

复制积压缓冲区会保存一部分最近传播的写命令，并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。

当从服务器断线重连主服务时，从服务器会通过 PSYNC 命令将自己的复制偏移量 offset 发送给主服务器，主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作。

* 如果 offset 之后的数据仍然在复制积压缓冲区，则主服务器对从服务器执行部分重同步操作。
* 反之，则主服务器对从服务器执行完整重同步操作。

复制积压缓冲区是由主服务器维护的一个固定长度的先进先出队列，默认大小为 1MB。

> 🔔 注意：合理调整复制积压缓冲区的大小
>
> Redis 复制积压缓冲区默认大小为 1MB。
>
> 复制积压缓冲区的最小大小可以根据公式 `second * write_size_per_second` 估算。

#### 服务器的运行 ID <a href="#fu-wu-qi-de-yun-hang-id" id="fu-wu-qi-de-yun-hang-id"></a>

* 每个 Redis 服务器，都有运行 ID，用于唯一识别身份。
* 运行 ID 在服务器启动时自动生成，由 40 个随机的十六进制字符组成。例如：132e358005e29741f8d7b0a42d666aace286edda

从服务器对主服务器进行初次复制时，主服务器会将自己的运行 ID 传送给从服务器，从服务器会将这个运行 ID 保存下来。

当从服务器断线重连一个主服务器时，从服务器会发送之前保存的运行 ID：

* 如果保存的运行 ID 和当前主服务器的运行 ID 一致，则说明从服务器断线之前连接的就是这个主服务器，主服务器可以继续尝试执行部分重同步操作；
* 反之，若运行 ID 不一致，则说明从服务器断线之前连接的不是这个主服务器，主服务器将对从服务器执行完整重同步操作。

### PSYNC 命令的实现 <a href="#psync-ming-ling-de-shi-xian" id="psync-ming-ling-de-shi-xian"></a>

![](https://139036132-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-Lx53lMutrsyPUks5pJf%2F-LyhXzxcDhGJR9iLcVIa%2F-LyhYIb48KIQWPoWcKwO%2Fimage.png?alt=media\&token=c7fe554d-5889-4a97-acab-786af201540a)

## 复制的实现 <a href="#fu-zhi-de-shi-xian" id="fu-zhi-de-shi-xian"></a>

### 1. 设置主从服务器 <a href="#id-1-she-zhi-zhu-cong-fu-wu-qi" id="id-1-she-zhi-zhu-cong-fu-wu-qi"></a>

配置一个从服务器非常简单， 只要在配置文件中增加以下的这一行就可以了：

```
slaveof 192.168.1.1 6379
```

当然， 你需要将代码中的 `192.168.1.1` 和 `6379` 替换成你的主服务器的 IP 和端口号。

另外一种方法是调用 [SLAVEOF host port](http://redisdoc.com/replication/slaveof.html#slaveof) 命令， 输入主服务器的 IP 和端口， 然后同步就会开始：

```
127.0.0.1:6379> SLAVEOF 192.168.1.1 10086
OK
```

### 2. 主从服务器建立 TCP 连接。 <a href="#id-2-zhu-cong-fu-wu-qi-jian-li-tcp-lian-jie" id="id-2-zhu-cong-fu-wu-qi-jian-li-tcp-lian-jie"></a>

### 3. 发送 PING 检查通信状态。 <a href="#id-3-fa-song-ping-jian-cha-tong-xin-zhuang-tai" id="id-3-fa-song-ping-jian-cha-tong-xin-zhuang-tai"></a>

### 4. 身份验证。 <a href="#id-4-shen-fen-yan-zheng" id="id-4-shen-fen-yan-zheng"></a>

如果主服务器没有设置 `requirepass` ，从服务器没有设置 `masterauth`，则不进行身份验证；反之，则需要进行身份验证。如果身份验证失败，则放弃执行复制工作。

如果主服务器通过 `requirepass` 选项设置了密码， 那么为了让从服务器的同步操作可以顺利进行， 我们也必须为从服务器进行相应的身份验证设置。

对于一个正在运行的服务器， 可以使用客户端输入以下命令：

```
config set masterauth <password>
```

要永久地设置这个密码， 那么可以将它加入到配置文件中：

```
masterauth <password>
```

另外还有几个选项， 它们和主服务器执行部分重同步时所使用的复制流缓冲区有关， 详细的信息可以参考 Redis 源码中附带的 `redis.conf` 示例文件。

### 5. 发送端口信息。 <a href="#id-5-fa-song-duan-kou-xin-xi" id="id-5-fa-song-duan-kou-xin-xi"></a>

从服务器执行 `REPLCONF listening-port <port-number>` ，向主服务器发送从服务器的监听端口号。

### 6. 同步。 <a href="#id-6-tong-bu" id="id-6-tong-bu"></a>

前文已介绍，此处不赘述。

### 7. 命令传播。 <a href="#id-7-ming-ling-chuan-bo" id="id-7-ming-ling-chuan-bo"></a>

在命令传播阶段，从服务器默认会以每秒一次的频率，向主服务发送命令：

```
REPLCONF ACK <replication_coffset>
```

命令的作用：

* 检测主从服务器的网络连接状态。
* 辅助实现 min-slave 选项。
* 检测命令丢失。

## 限制有 N 个以上从服务器才允许写入 <a href="#xian-zhi-younge-yi-shang-cong-fu-wu-qi-cai-yun-xu-xie-ru" id="xian-zhi-younge-yi-shang-cong-fu-wu-qi-cai-yun-xu-xie-ru"></a>

从 Redis 2.8 开始， 为了保证数据的安全性， 可以通过配置， 让主服务器只在有至少 N 个当前已连接从服务器的情况下， 才执行写命令。

不过， 因为 Redis 使用异步复制， 所以主服务器发送的写数据并不一定会被从服务器接收到， 因此， 数据丢失的可能性仍然是存在的。

以下是这个特性的运作原理：

* 从服务器以每秒一次的频率 PING 主服务器一次， 并报告复制流的处理情况。
* 主服务器会记录各个从服务器最后一次向它发送 PING 的时间。
* 用户可以通过配置， 指定网络延迟的最大值 `min-slaves-max-lag` ， 以及执行写操作所需的至少从服务器数量 `min-slaves-to-write` 。

如果至少有 `min-slaves-to-write` 个从服务器， 并且这些服务器的延迟值都少于 `min-slaves-max-lag`秒， 那么主服务器就会执行客户端请求的写操作。

你可以将这个特性看作 CAP 理论中的 C 的条件放宽版本： 尽管不能保证写操作的持久性， 但起码丢失数据的窗口会被严格限制在指定的秒数中。

另一方面， 如果条件达不到 `min-slaves-to-write` 和 `min-slaves-max-lag` 所指定的条件， 那么写操作就不会被执行， 主服务器会向请求执行写操作的客户端返回一个错误。

以下是这个特性的两个选项和它们所需的参数：

* `min-slaves-to-write <number of slaves>`
* `min-slaves-max-lag <number of seconds>`

详细的信息可以参考 Redis 源码中附带的 `redis.conf` 示例文件。

## Redis 复制要点 <a href="#redis-fu-zhi-yao-dian" id="redis-fu-zhi-yao-dian"></a>

* Redis 使用异步复制。 从 Redis 2.8 开始， 从服务器会以每秒一次的频率向主服务器报告复制流（replication stream）的处理进度。
* 一个主服务器可以有多个从服务器。
* 不仅主服务器可以有从服务器， 从服务器也可以有自己的从服务器， 多个从服务器之间可以构成一个图状结构。
* 复制功能不会阻塞主服务器： 即使有一个或多个从服务器正在进行初次同步， 主服务器也可以继续处理命令请求。
* 复制功能也不会阻塞从服务器： 只要在 `redis.conf` 文件中进行了相应的设置， 即使从服务器正在进行初次同步， 服务器也可以使用旧版本的数据集来处理命令查询。

  不过， 在从服务器删除旧版本数据集并载入新版本数据集的那段时间内， 连接请求会被阻塞。

  你还可以配置从服务器， 让它在与主服务器之间的连接断开时， 向客户端发送一个错误。
* 复制功能可以单纯地用于数据冗余（data redundancy）， 也可以通过让多个从服务器处理只读命令请求来提升扩展性（scalability）： 比如说， 繁重的 \[SORT key \[BY pattern] \[LIMIT offset count] \[GET pattern \[GET pattern …]] \[ASC | DESC] \[ALPHA] \[STORE destination]]\(<http://redisdoc.com/database/sort.html#sort>) 命令可以交给附属节点去运行。
* 可以通过复制功能来让主服务器免于执行持久化操作： 只要关闭主服务器的持久化功能， 然后由从服务器去执行持久化操作即可。
* 在使用 Redis 复制功能时的设置中，强烈建议在 master 和在 slave 中启用持久化。当不启用时，例如由于非常慢的磁盘性能而导致的延迟问题，**应该配置实例来避免重置后自动重启**。
* 从 Redis 2.6 开始， 从服务器支持只读模式， 并且该模式为从服务器的默认模式。
  * 只读模式由 `redis.conf` 文件中的 `slave-read-only` 选项控制， 也可以通过 [CONFIG SET parameter value](http://redisdoc.com/configure/config_set.html#config-set) 命令来开启或关闭这个模式。
  * 只读从服务器会拒绝执行任何写命令， 所以不会出现因为操作失误而将数据不小心写入到了从服务器的情况。

## 参考资料 <a href="#can-kao-zi-liao" id="can-kao-zi-liao"></a>

* 《Redis 实战》
* 《Redis 设计与实现》
* <http://redisdoc.com/topic/replication.html>
