一般情况下,我们都是使用单个Redis服务器。但是在生产环境中,单个Redis数据库可能存在如系统崩溃、网络连接闪断或者服务器断电等单点故障的问题。那么Redis是否提供了类似于Mysql的主从复制的机制呢?对的,Redis跟其他数据库也一样,同样提供了复制机制,使得一个Redis服务器【主实例或者Master】复制到一个或者多个Redis服务器中【从实例或Slave】.
复制机制不仅提供了容错的能力,还可以对系统进行水扩展。在一个侧重于读取的应用来说,多个从实例可以减轻Master实例的压力.
在单个Redis实例来说,Redis服务器都是以主示例的模式运行的。通过如下命令可以看出:
127.0.0.1:6379> INFO REPLICATION
# Replication
role:master
connected_slaves:0
master_replid:27e53ec844758c6e902480bfe22f1af576e1960c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
上面是单个Redis示例运行,角色为master
我们如果要配置一个从实例,需要为从实例准备一个配置文件,可以将redis.conf复制一份,并重新命令为redis-slave.conf,然后对下面的几个参数进行修改:
port 6380
pidfile /var/run/redis_6380.pid
dir ./slave #这个最好是绝对路径,我自己的系统配置了/apps/redis/slave
slaveof 127.0.0.1 6379
上面我们从实例的数据目录为./slave,所以一定要新建一个slave目录,在不存在的情况下。
当以上的配置文件都修改完后,开始启动:
[root@localhost redis]# /apps/redis/src/redis-server /apps/redis/redis-slave.conf
下面,分别进入主从的两个客户端:
#先进入主客户端
[root@localhost ~]# /apps/redis/src/redis-cli -p 6379
127.0.0.1:6379> INFO REPLICATION
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=462,lag=1
master_replid:e4ba167d75d5303ca4e8a9b72e73ea692ad5e20d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:462
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:462
#进入从客户端
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:7
master_sync_in_progress:0
slave_repl_offset:462
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:e4ba167d75d5303ca4e8a9b72e73ea692ad5e20d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:462
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:462
开始测试:
#在主示例新建一个键
127.0.0.1:6379> set "name" "zhang"
OK
127.0.0.1:6379> get name
"zhang"
#在从示例查看,数据同步成功
127.0.0.1:6380> get name
"zhang"
#如果我们在从实例修改该值,会报错,因为该从示例只能进行读,配置文件配置了slave-read-only yes,为了保证数据一致性,推荐从实例只进行读取
127.0.0.1:6380> set name abc
(error) READONLY You can't write against a read only slave.
#现在我们关闭从实例
127.0.0.1:6380> SHUTDOWN save
#再在主实例添加一个新键
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> get age
"20"
#重启从实例,再获取age的值
127.0.0.1:6380> get age
"20"
工作原理
首先我们给从实例配置了一个新的配置文件,让实例监听了6380端口,同时修改了数据存放的目录,并添加了一个slaveof 127.0.0.1 6379表示监听主实例。可以使用INFO REPLICATION命令来查看是否成功的建立了复制关系。master_replid表示主实例启动时生成的随机字符串,每次启动都会发生变化,也就是从实例都需要进行一次完全同步。master_repl_offset是复制流的一个偏移标记,这个跟随主实例上数据事件的发生不断增长。
当主从实例建立了网络连接后并成功建立复制关系,主实例会把将其接收到的所有写入命令转发给从实例执行,以实现主从实例之间的数据同步。
当主从实例第一次与主实例连接时会发生什么呢?从实例在网络连接断开后会重新连接到主实例么?在Redis的复制机制中,共有两种重新同步机制:完全重新同步和部分重新同步。当从实例启动并连接到主实例后,从实例会尝试通过发送master_replid和master_repl_offset请求进行部分重新同步。如果主实例接受部分重新同步的请求,那么它会从从实例停止时的最后一个偏移量出开始增量的进行命令同步。否则,就需要进行完全重新同步。当从实例第一次连接到它的主实例,总是需要进行完全重新同步。在进行完全同步时,为了将所有的数据复制到从实例中,主实例需要将数据转储到一个RDB文件中,然后将这个文件发送给从实例。从实例接收到这个文件后,会将内存中的所有数据清除,再讲RDB文件中的数据导入。主实例上的复制过程完全是异步的,因此不会阻塞服务器处理客户端请求。
下面我们看下,从实例第一次连接到主实例的日志:
2360:S 31 Jul 17:25:53.610 * Connecting to MASTER 127.0.0.1:6379
2360:S 31 Jul 17:25:53.611 * MASTER <-> SLAVE sync started
2360:S 31 Jul 17:25:53.611 * Non blocking connect for SYNC fired the event.
2360:S 31 Jul 17:25:53.612 * Master replied to PING, replication can continue...
2360:S 31 Jul 17:25:53.612 * Partial resynchronization not possible (no cached master)
2360:S 31 Jul 17:25:53.626 * Full resync from master: f91abe509765a068143f4d57ad64f6e9ea8942f8:0
2360:S 31 Jul 17:25:53.684 * MASTER <-> SLAVE sync: receiving 545 bytes from master
2360:S 31 Jul 17:25:53.684 * MASTER <-> SLAVE sync: Flushing old data
2360:S 31 Jul 17:25:53.684 * MASTER <-> SLAVE sync: Loading DB in memory
2360:S 31 Jul 17:25:53.685 * MASTER <-> SLAVE sync: Finished with success
第一次主服务器的日志:
2276:M 31 Jul 17:25:53.612 * Slave 127.0.0.1:6380 asks for synchronization
2276:M 31 Jul 17:25:53.612 * Full resync requested by slave 127.0.0.1:6380
2276:M 31 Jul 17:25:53.616 * Starting BGSAVE for SYNC with target: disk
2276:M 31 Jul 17:25:53.620 * Background saving started by pid 2364
2364:C 31 Jul 17:25:53.633 * DB saved on disk
2364:C 31 Jul 17:25:53.635 * RDB: 6 MB of memory used by copy-on-write
2276:M 31 Jul 17:25:53.684 * Background saving terminated with success
2276:M 31 Jul 17:25:53.684 * Synchronization with slave 127.0.0.1:6380 succeeded
主实例接受了来自从实例的完全重新同步请求后,创建了一个后台进程将数据保存到磁盘上,然后将数据发送给从实例。
下面是从实例部分同步日志:
2444:S 31 Jul 17:39:51.336 * Connecting to MASTER 127.0.0.1:6379
2444:S 31 Jul 17:39:51.336 * MASTER <-> SLAVE sync started
2444:S 31 Jul 17:39:51.336 * Non blocking connect for SYNC fired the event.
2444:S 31 Jul 17:39:51.337 * Master replied to PING, replication can continue...
2444:S 31 Jul 17:39:51.338 * Trying a partial resynchronization (request e4ba167d75d5303ca4e8a9b72e73ea692ad5e20d:970).
2444:S 31 Jul 17:39:51.339 * Successful partial resynchronization with master.
2444:S 31 Jul 17:39:51.340 * MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.
从日志可以看出,从实例使用了e4ba167d75d5303ca4e8a9b72e73ea692ad5e20d:970发出了一个部分重新同步的请求。主实例接受到这个请求后,并开始从backlog缓冲区发送位于970偏移处的命令。注意:重启后进行部分重新同步时Redis4.0后的一个新特性。在Redis4.0的是实现中,master_replid和offset存储在RDB文件中。当从实例优雅地进行关闭后并重新启动后,Redis能够从RDB文件中重新加载master_replid和offset,从而可以进行部分重新同步。
当一个从实例提升为主实例后,其他从实例必须要与新主实例进行重新同步。在Redis4.0之前,只要master_replid发生改变,这个过程就需要一个完全的重新同步。在Redis4.0后,新主实例会记录旧主实例的master_replid和offset,所以其他从实例可以进行部分重新同步请求,即便是master_replid发生改变。更加具体的说,当主实例发生故障迁移时,在新的主实例上master_replid,master_repl_offset将被复制为master_replid2,second_repl_offset