今天来聊一下Redis的主从复制。Redis的主从复制跟关系型数据库(比如mysql)的主从复制类似。主从复制由于能够实现读写分离和从机实现对主机的数据备份的特点,所以在一定程度上是可以扩展Redis的性能的。
主从复制的实现
接下来,就看下Redis主从复制的实现。我们配置一个主机Master和两个从机Slave:
- 127.0.0.1:6379(Master)
- 127.0.0.1:6380(Slave)
- 127.0.0.1:6381(Slave)
我们把Redis这个目录拷贝两份,分别改名为Redis_6380和Redis_6381,原来那个目录保持不变。然后分别打开Redis_6380和Redis_6381目录下的redis.windows.conf文件,分别按如下修改:
从机1:
- port 6380
- slaveof 127.0.0.1 6379
从机2:
- port 6381
- slaveof 127.0.0.1 6379
打开命令行,到各自目录下启动服务:
#主机master
D:\soft\Redis>redis-server.exe redis.windows.conf
#从机slave1
D:\soft\Redis_6380>redis-server.exe redis.windows.conf
#从机slave2
D:\soft\Redis_6381>redis-server.exe redis.windows.conf
然后输入如下命令,分别进入三个实例的控制台:
#主机master
C:\Users\liukai>redis-cli -p 6379
#从机slave1
C:\Users\liukai>redis-cli -p 6380
#从机slave2
C:\Users\liukai>redis-cli -p 6381
分别在三个实例下执行命令:info replication,分别查看三个服务的信息:
我们可以看到6379的主机有两个从机,分别为6380和6381;而6380和6381查看到的信息,主机都是6379。
#主机master
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=294,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=294,lag=1
master_replid:4ae5e4ab7eb37d4df74c2a8a113b469ea6ed57bb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:294
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:294
#从机slave1
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_repl_offset:280
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4ae5e4ab7eb37d4df74c2a8a113b469ea6ed57bb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:280
#从机slave2
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:266
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4ae5e4ab7eb37d4df74c2a8a113b469ea6ed57bb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:266
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:43
repl_backlog_histlen:224
我们随便在主机添加一个key测试下,看数据有没有同步到从机。测试命令如下,我们看到在主机添加key1后,数据同步到了两个从机。
#主机master
127.0.0.1:6379> keys key1
(empty list or set)
127.0.0.1:6379> set key1 redis
OK
127.0.0.1:6379> keys key1
1) "key1"
127.0.0.1:6379> get key1
"redis"
#从机slave1
127.0.0.1:6380> keys key1
(empty list or set)
127.0.0.1:6380> keys key1
1) "key1"
127.0.0.1:6380> get key1
"redis"
#从机slave2
127.0.0.1:6381> keys key1
(empty list or set)
127.0.0.1:6381> keys key1
1) "key1"
127.0.0.1:6381> get key1
"redis"
主从复制的机制
Redis主从复制的机制主要有以下三点:
-
当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave 。包括客户端的写入、key 的过期或被逐出等等。
-
当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步,这意味着它会尝试只获取在断开连接期间内丢失的命令流。
-
当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。
我们再来看看Redis主从复制的一些特点:
-
Redis 使用异步复制,slave 和 master 之间异步地确认处理的数据量。其特点是低延迟和高性能。
-
一个 master 可以拥有多个 slave。
-
slave 可以接受其他 slave 的连接。除了多个 slave 可以连接到同一个 master 之外, slave 之间也可以像层叠状的结构(cascading-like structure)连接到其他 slave 。自 Redis 4.0 起,所有的 sub-slave 将会从 master 收到完全一样的复制流。
-
Redis 复制在 master 侧是非阻塞的。这意味着 master 在一个或多个 slave 进行初次同步或者是部分重同步时,可以继续处理查询请求。
-
复制在 slave 侧大部分也是非阻塞的。当 slave 进行初次同步时,它可以使用旧数据集处理查询请求,假设你在 redis.conf 中配置了让 Redis 这样做的话。否则,你可以配置如果复制流断开, Redis slave 会返回一个 error 给客户端。但是,在初次同步之后,旧数据集必须被删除,同时加载新的数据集。 slave 在这个短暂的时间窗口内(如果数据集很大,会持续较长时间),会阻塞到来的连接请求。自 Redis 4.0 开始,可以配置 Redis 使删除旧数据集的操作在另一个不同的线程中进行,但是,加载新数据集的操作依然需要在主线程中进行并且会阻塞 slave 。
-
复制既可以被用在可伸缩性,以便只读查询可以有多个 slave 进行(例如 O(N) 复杂度的慢操作可以被下放到 slave ),或者仅用于数据安全。
-
可以使用复制来避免 master 将全部数据集写入磁盘造成的开销:一种典型的技术是配置你的 master redis.conf 以避免对磁盘进行持久化,然后连接一个 slave ,其配置为不定期保存或是启用 AOF。但是,这个设置必须小心处理,因为重新启动的 master 程序将从一个空数据集开始:如果一个 slave 试图与它同步,那么这个 slave 也会被清空,如果生产出现了这种事就赶紧订个站票连夜跑路吧。
主从复制的安全性
在使用 Redis 复制功能时的设置中,强烈建议在 master 和在 slave 中启用持久化。当不可能启用时,例如由于非常慢的磁盘性能而导致的延迟问题,应该配置实例来避免重置后自动重启。
为了更好地理解为什么关闭了持久化并配置了自动重启的 master 是危险的,检查以下故障模式,这些故障模式中数据会从 master 和所有 slave 中被删除:
-
我们设置节点 A 为 master 并关闭它的持久化设置,节点 B 和 C 从 节点 A 复制数据。
-
节点 A 崩溃,但是他有一些自动重启的系统可以重启进程。但是由于持久化被关闭了,节点重启后其数据集合为空。
-
节点 B 和 节点 C 会从节点 A 复制数据,但是节点 A 的数据集是空的,因此复制的结果是它们会销毁自身之前的数据副本。
当 Redis Sentinel 被用于高可用并且 master 关闭持久化,这时如果允许自动重启进程也是很危险的。例如, master 可以重启的足够快以致于 Sentinel 没有探测到故障,因此上述的故障模式也会发生。
任何时候数据安全性都是很重要的,所以如果 master 使用复制功能的同时未配置持久化,那么自动重启进程这项应该被禁用。
如何工作
每一个 Redis master 都有一个 replication ID :这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master 也持有一个偏移量,master 将自己产生的复制流发送给 slave 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新 slave 的状态。复制偏移量即使在没有一个 slave 连接到 master 时,也会自增,所以基本上每一对给定的
Replication ID, offset
都会标识一个 master 数据集的确切版本。我们上面用info replication命令查看时,可以看到有主机和从机的replid、offset。
当 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 服务。
好了,Redis的主从复制就讲到这里,我们大概了解了主从复制的原理、如何保证数据的安全以及其的工作方式。