Redis的复制是通过主从复制(master-slave replication)模式实现的,它允许Redis服务器创建出多个拥有数据库副本的服务器,其中Redis服务器被称为主服务器(master server),而存储数据库副本的服务器被称为从服务器(slave server,或称为replica)。每次当 slave 和 master 之间的连接断开时,slave 会自动重连到 master 上,并且无论这期间 master 发生了什么,slave都将尝试让自身成为master的精确副本。Redis的复制功能主要依靠以下3个机制:
(1).当一个 master实例和一个slave 实例连接正常时,master会发送一连串的命令流来保持对slave的更新,以便于将自身数据集的改变复制给slave,包括客户端的写入、key 的过期或被逐出等等。
(2).当master和slave之间的连接断开之后,因为网络问题、或者是主从意识到连接超时,slave重新连接上master并会尝试进行部分重同步,这意味着它会尝试只获取在断开连接期间内丢失的命令流。
(3).当无法进行部分重同步时,slave会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave,之后在数据集更改时持续发送命令流到 slave。
1.将服务器设置为从服务器
首先在Redis安装包拷贝一份redis.conf名为redis-my.conf,编辑redis-my.conf将port修改为12345。
1.1 使用replicaof命令将服务器设置为从服务器
# 进入redis根目录先启动主服务器
./src/redis-server redis.conf
# 然后启动从服务器
./src/redis-server redis-my.conf
# 进入Redis的src目录
# 首先使用redis-cli连接到redis-server作为主服务器客户端,ip为172.16.178.128,端口为6379,密码为123456
redis-cli -h 172.16.178.128 -p 6379 -a 123456
# 使用redis-cli连接到redis-server作为从服务器客户端
redis-cli -h 172.16.178.128 -p 12345 -a 123456
在Redis5.0之前Redis使用slaveof命令作为复制命令,Redis5.0之后将slaveof改名为replicaof命令。服务端接收到replicaof命令之后,主从服务器将执行数据同步操作,从服务器原有的数据将被情况,取而代之的是主服务器传送过来的数据副本,同步完成之后主从服务器将拥有相同的数据。
# replicaof命令语法
replicaof host port
# 在从客户端使用replicaof命令与主服务器连接复制连接,因为Redis的操作是以异步方式进行的,所以执行replicaof命令立即向客户端返回ok
replicaof 172.16.178.128 6379 #打印:OK
# 因为主服务器设置了密码(否则你会发生主从复制不生效),所以需要设置一个 slave 对 master 进行验证,如果要在当前运行的实例上进行验证操作,执行以下命令即可;如果想永久设置则可以在从服务器的配置文件设置masterauth <password>
config set masterauth 123456
#################测试主从复制是否成功
# 在主客户端执行
set name "zxp"
get name #打印:"zxp"
# 在从客户端打印
get name #打印:"zxp"。如果打印"zxp"则说明成功
del name #打印:(error)READONLY You can't write against a read only replica(不能对只读副本进行写入)
若是是让从服务器取消复制,执行以下命令即可让从服务器停止复制,重新变成主服务器。服务器在停止复制之后不会清空数据库,而是会继续保留复制产生的所有的数据库。
# 取消复制
replicaof no one # 打印:OK
1.2 启动redis-server通过配置选项设置从服务器
# 使用redis-cli连接到服务器,通过配置选项设置当前服务器为从服务器(进入到Redis根目录),--replicaof指定主服务器的ip和端口
./src/redis-server redis-my.conf --replicaof 172.16.178.128 6379
# 设置一个 slave 对 master 进行验证
config set masterauth 123456
######################## 验证主从复制
#### 主服务器客户端
172.16.178.128:6379> set name "zxp666"
172.16.178.128:6379> get name #打印:"zxp666"
#### 从服务器客户端
172.16.178.128:12345> get name #打印:"zxp666",打印"zxp666"说明主从复制成功了
1.3 使用role命令服务器的角色
ROLE命令可以用来查看服务器当前担任的角色:
# 语法
ROLE
# 例子:主服务器使用role命令
172.16.178.128:6379> role
1) "master"
2) (integer) 2381
3) 1) 1) "172.16.178.128"
2) "12345"
3) "2381"
# 例子:从服务器使用role命令
172.16.178.128:12345> role
1) "slave"
2) "172.16.178.128"
3) (integer) 6379
4) "connected"
5) (integer) 2325
主服务器执行role命令得出结果由3个元素组成的数组:
元素1:返回当前服务器所处角色,返回”master”表示主服务器,返回”slave”表示从服务器。
元素2:返回当前服务器的复制偏移量(replication offset),它是一个整数.记录了主服务器目前向复制数据流发送的数据数量。
元素3:元素3是一个数组,它记录了主服务器属下的所有从服务器。这个数组的每个元素都由3个子元素组成,第一个子元素是从服务器的ip地址,第二个子元素是从服务器的端口,而第三个子元素则为从服务器的的复制偏移量。从服务器的复制偏移量记录了从服务器通过复制数据流接收到的复制数据数量,当从服务器的复制偏移量与主服务器的复制偏移量保持一致时,它们的数据就是一致的。
从服务器执行的role命令得出结果由5个元素组成的数组:
元素1:返回当前服务器所处角色,返回”master”表示主服务器,返回”slave”表示从服务器。
元素2和元素3:记录了当前从服务器正在复制的主服务器的IP和端口
元素4:表示主服务器与从服务器的连接状态,它有以下几种情况:
- “none”:主从服务器尚未建立连接。
- “connect”:主从服务器正在握手。
- “sync”:主从服务器正在进行数据同步。
- “connected”:主从服务器已经进入在线更新状态。
- “unknown”:主从服务器已经进入在线更新状态。
2.Redis主从复制原理
Redis的主从复制可以根据是否是全量分为全量同步(完整同步)和增量同步(部分同步)。
2.1 完整同步
当一个Redis服务器接收到replicaof命令,开始对另一个服务器进行复制时,主从服务器会进行如下操作:
(1).slave(从)服务器连接master服务器,发送SYNC(新版的Redis使用的是PSYNC协议,但SYNC协议仍被向后兼容)命令。
(2).master服务器接收到slave发送的SYNC命令,执行BGSAVE命令生成一个RDB文件,并使用缓冲区存储在BGSAVE命令之后执行的所有写命令。
(3).当RDB文件创建完毕后,master服务器通过socket(套接字)将RDB文件发送给所有slave服务器。(RDB说白了就是记录某一个时间段操作记录的快照文件)
(4).slave服务器接收master服务器发送过来的RDB文件后,会丢弃slave服务器中所有旧数据,然后载入master发送的RDB文件,从而获得master服务器在执行BGSAVE命令时的所有数据。
(5).当从服务器完成RDB文件载入操作,并开始上线接收命令请求时,master服务器就会把之前存储在缓存区中的所有写命令发送给从服务器执行。
通过创建、发送并载入RDB文件来达成主从服务器数据一致的方式我们称为完整同步。每个从服务器在刚开始进行复制时,都需要与主服务器进行一次完整同步。
进行数据同步是重用RDB文件
为了提高数据同步操作的执行效率,如果主服务器在接收到REPLICAOF命令之前已经完成了一次RDB创建操作,并且它的数据库在创建RDB文件之后没有发生过任何变化,那么主服务器将直接向从服务器发送已有的RDB文件,以此来避免无谓的RDB文件生成操作。
此外,如果在主服务器创建RDB文件期间,有多个从服务器向主服务器发送数据同步请求,那么主服务器将把发送请求的从服务器全部放入队列中,等到RDB文件创建完毕之后,再把它发送给队列中的所有从服务器,以此来复用RDB文件并避免多余的RDB文件创建操作。
在线更新
主从服务器在执行完完整同步操作时,它们的数据就是一致性的,但这种一致性不是永久的,当主服务器执行了新的写的命令时,主从服务器的数据就会不一致,为了让主从服务器的数据一致性可以一直拥有相同的数据,Redis会对从服务器进行在线更新,每当主服务器执行完一个写命令之后,它就会将该命令或具有相同效果的写命令发送给从服务器执行。因为在线更新是异步进行的,所以在主服务器执行完命令后,直到从服务器也执行完相同的写命令这段时间里,主从服务器的数据将会出现短暂的不一致,因此若要获取最新的数据需要直接读取主服务器的数据,而不是从服务器的数据。
无硬盘复制
主从服务器进行完整同步时,需要在本地创建RDB文件,然后通过套接字将这个RDB文件发送给从服务器。如果主服务器所在宿主机的硬盘负载非常大或性能不佳,那么创建RDB文件引起的大量硬盘写入将对主服务器的性能造成影响,并导致复制过程变慢。为了解决此问题,Redis2.8.18引入了无须磁盘的复制特性(disless replication),启用了这个特性的主服务器在接收到REPLICAOF命令时将不会在本地创建RDB文件,而是会派生一个子进程通过套接字将RDB文件写入从服务器。这样主服务器就可以在不创建RDB文件的情况下,完成与从服务器的数据同步。
两种启用无需硬盘的复制特性如下:
(1).在redis配置文件中配置repl-diskless-sync选项的值为yes。
repl-diskless-sync <yes|no>
(2).在启动redis-server时指定—repl-diskless-sync为yes
./redis-server --repl-diskless-sync yes
注意:无须硬盘的复制特性只是避免了在主服务器上创建RDB文件,但仍需要在从服务器上创建RDB文件,Redis目前还无法在完全不使用硬盘的情况下完成完整数据同步。
2.2 部分同步
当因故障下线的从服务器重新上线时,主从服务器的数据通常会出现数据不一致的情况,为了使主从服务器数据一致,它们必须重新进行同步。在Redis2.8版本前,重新同步操作是通过直接进行完整同步来实现的,但是完整同步在从服务器只是短暂下线的情况下是非常浪费资源的,例如:主从服务器断开期间主服务器就多执行了几个写命令,如果使用完整同步是非常低效的。为了解决此问题,Redis从2.8开始使用新的重同步功能区代替原来的重同步功能,流程如下:
(1).当一个Redis服务器成为另一个服务器的主服务器时,它会把每个被执行的写命令都记录到一个特定长度的先进先出的队列中。
(2).当断线的从服务器尝试重写连接主服务器时,主服务器将检查从服务器短线期间,被执行的那些写命令是否仍存在队列中,如果是主服务器就会直接把从服务器缺失的那些写命令发送给从服务器执行,从服务器通过执行这些写命令就可以重写与主服务器保持一致,这样既可避免重新进行完整同步的麻烦。
(3).如果从服务器缺失的那些命令已经不存在队列中,那么主从服务器将进行一次完整的同步。
当记录主服务器写命令的队列体积越大时,它能够记录的写命令就越多,从服务器断线后与主服务器数据一致性的概率也就越大。Redis为这个队列设置默认大小为1MB,可以通过在redis配置文件配置repl-backlog-size来修改这个队列的大小。