Redis主从同步原理
2018-08-30 09:58:01 阿炯

为了提升 Redis 的读性能,通常我们可以为一个master实例增加一个或多个slave实例,这就是主从复制。另一方面,主从复制提供了数据冗余,这在一定程度上提高了 Redis 的可用性,例如,当master实例发生故障时,我们可以将其中一个slave实例提升为master,从而继续保证 Redis 的可用性。

主从复制原理


在设置好主从复制之后,一开始 slave 与 master 之间的数据是不同步的,为了让 slave 与 master 之间保存数据同步,slave 会发送一个 PSYNC 命令给 master。由于 slave 是初次连接上 master,所以 master 接收到 PSYNC 命令之后会执行完整重同步(full resynchronization),具体的过程是这样的:首先 master 会执行 BGSAVE 命令,在后台生成一个 RDB 文件,与此同时开辟一个缓冲区用于存储 client 发来的命令。当 BGSAVE 执行成功之后,master 会将这个 RDB 文件发送给 slave,slave 在接收到这个 RDB 文件之后会将它加载到内存。接着 master 会将缓冲区内的所有命令发送给 slave 去执行。

下面分步骤详述:
1.Slave启动后,无论是第一次连接还是重连到Master,它都会主动发出一个SYNC命令

2.当Master收到SYNC命令之后,将会执行BGSAVE(后台存盘进程),即在后台保存数据到磁盘(rdb快照文件),同时收集所有新收到的写入和修改数据集的命令存入缓冲区(非查询类)

3.Master在后台把数据保存到快照文件完成后,会传送整个数据库文件到Slave

4.Slave接收到数据库文件后,会把内存清空,然后加载该文件到内存中以完成一次完全同步

5.然后Master会把之前收集到缓冲区中的命令和新的修改命令依次传送给Slave

6.Slave接受到之后在本地执行这些数据修改命令,从而达到最终的数据同步

7.之后Master与Slave之间将会不断的通过异步方式进行命令的同步,从而保证数据的时时同步

8.如果Master和Slave之间的连接出现断连,Slave可以自动重连Master

根据版本的不同,断连后同步的方式也不同:
2.8之前:重连成功之后,一次全量同步操作将被自动执行。2.8之后:重连成功之后,进行部分同步操作。

配置

Redis的主从配置非常简单,只需要修改slave节点的redis.conf文件添加salveof。

# vi redis.conf
# slaveof <masterip> <masterport>
slaveof 192.168.18.128 6379
masterauth <master-password>#认证密钥

Redis主从复制是如何工作的

1)Redis使用异步复制。但从Redis 2.8开始,从服务器会周期性的应答从复制流中处理的数据量。

2)一个主服务器可以有多个从服务器。

3)从服务器也可以接受其他从服务器的连接。除了多个从服务器连接到一个主服务器之外,多个从服务器也可以连接到一个从服务器上,形成一个图状结构。

4)Redis主从复制不阻塞主服务器端。也就是说当若干个从服务器在进行初始同步时,主服务器仍然可以处理请求。

5)主从复制也不阻塞从服务器端。当从服务器进行初始同步时,它使用旧版本的数据来应对查询请求,假设你在redis.conf配置文件是这么配置的。否则的话,你可以配置当复制流关闭时让从服务器给客户端返回一个错误。但是,当初始同步完成后,需要删除旧的数据集和加载新的数据集,在这个短暂的时间内,从服务器会阻塞连接进来的请求。

6)主从复制可以用来增强扩展性,使用多个从服务器来处理只读的请求(比如,繁重的排序操作可以放到从服务器去做),也可以简单的用来做数据冗余。

7)使用主从复制可以为主服务器免除把数据写入磁盘的消耗:在主服务器的redis.conf文件中配置“避免保存”(注释掉所有“保存“命令),然后连接一个配置为“进行保存”的从服务器即可。但是这个配置要确保主服务器不会自动重启。

下面我们就通过一个图来概述在每一个阶段中,slave究竟做了些什么:


关于上图,有一点说明下:redis接收到slaveof master_host master_port命令后并没有马上与master建立连接,而是当执行服务器例行任务serverCron,发现自己正处于REDIS_REPL_CONNECT状态,这时才真正的向maser发起连接。

接着我们来看下主从复制过程中,master这边的流程是如何,在具体看细节之前,我们先综合来看master这边主要做的几件事情:


看完这个图,你也许会有以下几个疑问:

1. 为什么在master发送完RDB文件后,还要定期的向slave发送PING命令?

2. 在发送完RDB文件之后,master发送的“变更”命令又是什么,有什么用?

在回答问题之前1,我们先回答问题2:
master保存RDB文件是通过一个子进程进行的,所以master依然可以处理客户端请求而不被阻塞,但这也导致了在保存RDB文件期间,“键空间”可能发生变化(譬如接收到一个客户端请求,执行"set name freeoa"命令),因此为了保证数据同步的一致性,master会在保存RDB文件期间,把接受到的这些可能变更数据库“键空间”的命令保存下来,然后放到每个slave的回复列表中,当RDB文件发送完master会发送这些回复列表中的内容,并且在这之后,如果数据库发生变更,master依然会把变更的命令追加到回复列表发送给slave,这样就可以保证master和slave数据的一致性!

由于在发送完RDB文件之后,master会不定时的给slave发送“变更”命令,可能过1s,也可能过1小时,所以为了防止slave无意义等待(譬如master已经挂掉的情况),master需要定时发送“保活”命令PING,以此告诉slave:我还活着,不要中断与我的连接。

现在我们就看下,当master接受到slave发送的sync同步命令后究竟发生了哪些事:


上图看似分支复杂,但我们抓住以下几点即可:

1.保存RDB文件是在一个子进程中进行的

2.如果master已经在保存RDB文件,但是没有客户端正在等待这次BGSAVE,新添加的slave需要等到下次BGSAVE,而不能直接使用这次生成的RDB文件(原因图中已经说明)

3.master会定期检查RDB文件是否保存完毕(时间事件serverCron)

接下来我们看下,master是如何给每一个slave发送RDB文件的:


好了,至此我们已经分析完在主从复制过程中,master和slave两边分别是怎么一个处理流程;最后,我绘制了一个图,综述了主从复制这一过程(我们可以边看图,边回忆其中的具体细节):.


注意:在主从复制过程中,任何一步发生错误,都会导致整个过程重头开始,所以若RDB文件很大又或是此时正处在业务高峰期,对系统性能将会有非常大的影响!


部分重新同步(复制)

从Redis 2.8开始,如果遭遇连接断开,重新连接之后可以从中断处继续进行复制,而不必重新同步。

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

Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Master run id)。当网络断开,Slave尝试重连时:

a.如果MasterID相同(即仍是断网前的Master服务器),并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在,则Master会将缺失的这段时间的所有命令发送给Slave执行,然后复制工作就可以继续执行了;

b. 否则,依然需要全量复制操作;

Redis 2.8 的这个部分重同步特性会用到一个新增的 PSYNC 内部命令,而 Redis 2.8 以前的旧版本只有 SYNC 命令, 不过只要从服务器是 Redis 2.8 或以上的版本,它就会根据主服务器的版本来决定到底是使用 PSYNC 还是 SYNC。部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令。Redis2.8版本可以检测出它所连接的服务器是否支持PSYNC命令,不支持的话使用SYNC命令。


无磁盘复制

通常来讲,一个完全重新同步需要在磁盘上创建一个RDB文件,然后加载这个文件以便为从服务器发送数据。

如果使用比较低速的磁盘,这种操作会给主服务器带来较大的压力。Redis从2.8.18版本开始尝试支持无磁盘的复制。使用这种设置时,子进程直接将RDB通过网络发送给从服务器,不使用磁盘作为中间存储。

关于部分重新同步,还有一些针对复制内存缓冲区的优化参数,可查看Redis介质中的Redis.conf示例获得更多信息。使用repl-diskless-sync配置参数来启动无磁盘复制。使用repl-diskless-sync-delay 参数来配置传输开始的延迟时间,以便等待更多的从服务器连接上来。查看Redis介质中的redis.conf示例获得更多信息。

只读从服务器

从Redis 2.6开始,从服务器支持只读模式,并且是默认模式。这个行为是由Redis.conf文件中的slave-read-only 参数控制的,可以在运行中通过CONFIG SET来启用或者禁用。

只读的从服务器会拒绝所有写命令,所以对从服务器不会有误写操作。但这不表示可以把从服务器实例暴露在危险的网络环境下,因为像DEBUG或者CONFIG这样的管理命令还是可以运行的。不过你可以通过使用rename-command命令来为这些命令改名来增加安全性。

你可能想知道为什么只读限制还可以被还原,使得从服务器还可以进行写操作。虽然当主从服务器进行重新同步或者从服务器重启后,这些写操作都会失效,还是有一些使用场景会想从服务器中写入临时数据的,但将来这个特性可能会被去掉。


限制有多个以上从服务器才允许写入

从Redis 2.8版本开始,可以配置主服务器连接N个以上从服务器才允许对主服务器进行写操作。但因为Redis使用的是异步主从复制,没办法确保从服务器确实收到了要写入的数据,所以还是有一定的数据丢失的可能性。

这一特性的工作原理如下:
1)从服务器每秒钟ping一次主服务器,确认处理的复制流数量。
2)主服务器记住每个从服务器最近一次ping的时间。
3)用户可以配置最少要有N个服务器有小于M秒的确认延迟。
4)如果有N个以上从服务器,并且确认延迟小于M秒,主服务器接受写操作。

还可以把这看做是CAP原则(一致性,可用性,分区容错性)不严格的一致性实现,虽然不能百分百确保一致性,但至少保证了丢失的数据不会超过M秒内的数据量。

如果条件不满足,主服务器会拒绝写操作并返回一个错误。
1)min-slaves-to-write(最小从服务器数)
2)min-slaves-max-lag(从服务器最大确认延迟)


参考来源


Redis主从复制原理与用法


Redis主从复制下的工作原理梳理