本篇博客是Redis系列的第4篇,主要讲解下Redis的主从复制机制。面试
本系列的前3篇能够点击如下连接查看:redis
Redis系列(二):Redis的5种数据结构及其经常使用命令数据库
Redis系列(三):Redis的持久化机制(RDB、AOF)服务器
Redis的主从复制是面试中常常会被问的,我最近面试的几家公司只要聊到Redis,都会问我主从复制的原理。网络
在本系列的上一篇博客中,咱们讲到了Redis的持久化机制,它很好的解决了单台Redis服务器因为意外状况致使Redis服务器进程退出或者Redis服务器宕机而形成的数据丢失问题。数据结构
但持久化机制还原数据有个前提:你的Redis服务器得能正常启动。并发
若是遇到极端的断电状况(虽然几率小,可是有可能),Redis服务器启都启动不了,怎么还原数据?怎么保证它的高可用。post
就算Redis服务器能启动了,网络链接也有崩掉的可能,我不信你没看到过电缆被挖断致使的某些服务不可用的新闻。性能
正是因为有这样的风险,因此生产环境Redis服务器不可能使用单台的,那既然使用多台Redis服务器,多台Redis服务器之间的数据如何同步呢?
这就须要用到Redis的复制机制。
还有个缘由就是,虽然Redis的性能很好,但单台毕竟仍是有瓶颈的,使用主从复制能够实现读写分离,提升Redis的高可用性,即主服务器用来执行写命令,多个从服务器用来执行读命令,相似于数据库的读写分离。
综上所述,主从复制主要有如下2个使用场景:
首先,我在本机开启2个Redis实例(也能够搞2台Redis服务器),分别为127.0.0.1:637九、127.0.0.1:6380。
而后,使用redis-cli
链接Redis实例127.0.0.1:6380并执行以下命令:
SLAVEOF 127.0.0.1 6379
复制代码
此时,咱们称127.0.0.1:6379为127.0.0.1:6380的主服务器(master),称127.0.0.1:6380为127.0.0.1:6379的从服务器(slave)。
2者之间的关系以下所示:
而后,咱们在主服务器上执行以下写命令:
SET msg "hello world"
复制代码
此时,咱们不只能在主服务器上获取到该值,也能在从服务器上获取到该值:
而后,咱们在主服务器上执行以下删除命令:
DEL msg
复制代码
此时,咱们会发现不只主服务器上的msg键被删除,从服务器上的msg也被删除:
因此说,进行复制中的主从服务器双方的数据库将保存相同的数据。
值得注意的是,从服务器只能执行读命令,执行写命令时会报以下错误:
若是从服务器不想再复制主服务器,能够执行命令:SLAVEOF no one
。
这里的旧版指的是Redis 2.8之前的版本。
Redis的复制功能分为如下2个操做:
当客户端向从服务器发送SLAVEOF
命令,要求从服务器复制主服务器时,从服务器会向主服务器SYNC
命令,该命令的执行步骤以下所示:
SYNC
命令。SYNC
命令后,执行BGSAVE
命令,在后台生成RDB文件,并使用一个缓冲区记录从如今开始执行的全部写命令。BGSAVE
命令执行完成,主服务器将生成的RDB文件发送给从服务器,从服务器接收并载入这个RDB文件,至此,从服务器的数据库状态和主服务器执行BGSAVE
命令时的数据库状态一致。SYNC
命令执行期间,主从服务器的通讯过程以下图所示:
在同步操做执行完毕后,主从服务器的数据库状态达到一致状态,当主服务器执行了客户端发送的写命令时,主服务器的数据库就被修改了,致使主从服务器的数据库状态再也不一致。
为了让主从服务器的数据库状态再次回到一致状态,主服务器须要对从服务器执行命令传播操做:主服务器会将本身执行的写命令,发送给从服务器执行,当从服务器执行了相同的写命令后,主从服务器的数据库状态再次回到一致状态。
举个具体的例子,好比主从服务器刚开始都拥有k一、k二、k三、k四、k5这5个键,而后客户端往主服务器发送了命令DEL k3
,此时主服务器会执行该条命令,并将该条命令传播给从服务器执行,从而使主从服务器的数据库状态保持一致。
整个变化过程以下所示:
这里的旧版指的是Redis 2.8之前的版本。
在Redis 2.8之前,从服务器对主服务器的复制分为如下2种状况:
初次复制
从服务器之前没有复制过任何主服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不一样。
断线后重复制
处于命令传播阶段的主从服务器由于网络缘由而中断了复制,但从服务器经过重试又从新连上了主服务器,并继续复制主服务器。
旧版复制功能能够很好的完成初次复制,但完成断线后重复制的效率却很低。
举个具体的例子,从服务器B一直在复制着主服务器A,刚开始都是正常的,主服务器A执行的写命令也都经过命令
传播的方式传递给了从服务器B执行,但忽然由于网络缘由,主服务器A和从服务器B之间中断了复制,在这期间,
假设主服务器又执行了10个写命令,而后从服务器B经过重试又从新连上了主服务器A,继续开始复制,那么它是
怎么复制的呢?
从服务器B会向主服务器A发送SYNC
命令,主服务器A接收到命令后会执行BGSAVE
命令,BGSAVE
命令执行期间的
全部写命令会被记录到缓冲区,待BGSAVE
命令执行完毕后,主服务器A会将生成的RDB文件发送给从服务器B,
从服务器B接收并载入这个RDB文件,而后主服务器A将缓冲区里的写命令发送给从服务器B执行,至此,主从
服务器的数据库状态又恢复一致,后续又进入命令传播阶段。
也就是说,每次断线后重复制,都要执行一次SYNC
命令来一次全量复制,但其实从服务器B须要的只是断开链接期间主服务器A执行的写命令,按上面的例子,也就是只须要10个写命令便可。
而SYNC
命令又是一个很是耗费资源的操做:
BGSAVE
命令生成RDB文件,这会耗费主服务器大量的CPU、内存和磁盘IO资源。这里的新版指的是Redis 2.8以及以后的版本。
从Redis 2.8版本开始,Redis使用PSYNC
命令代替SYNC
命令来执行复制时的同步操做。
PSYNC
命令有如下2种场景:
完整重同步
完整重同步用于处理初次复制,执行步骤和SYNC
命令的执行步骤基本同样。
部分重同步
部分重同步用于处理断线后重复制,当从服务器在断线后从新链接主服务器时,若是条件容许,主服务器能够将主从服务器链接断开期间执行的写命发送给从服务器,从服务器只要接收并执行这些写命令,就能够将数据库更新至主服务器当前所处的状态。
仍然用上面举的例子,新版复制,主服务器只须要把断开期间执行的10个写命令发送给从服务器便可,而不用生成并发送整个RDB文件,性能大大提高。
主从服务器在执行部分重同步时的通讯过程以下图所示:
那么部分重同步是如何实现的呢?
部分重同步功能由如下3个部分组成:
接下来咱们一一讲解。
执行复制的主服务器和从服务器会分别维护一个复制偏移量:
举个例子,假设主服务器有3个从服务器,它们的复制偏移量都为10086,以下图所示:
而后,主服务器向3个从服务器传播了长度为33字节的数据,那么主服务器的复制偏移量会加上33,变为10119,
从服务器A在这时恰好断线了,没有接收到数据,因此偏移量仍然为10086,
从服务器B和从服务器C正常接收到了数据,因此偏移量都更新为了10019,以下图所示:
很显然,经过对比主从服务器的复制偏移量,能够很容易地知道主从服务器是否处于一致状态。
而后,从服务器A经过重试又从新链接到了主服务器,而后向主服务器发送PSYNC命令,并报告了本身当前的复制
偏移量为10086,主服务器此时须要处理2个问题:
带着这2个问题,咱们看下复制积压缓冲区。
复制积压缓冲区是主服务器维护的一个固定长度先进先出队列,默认大小为1MB。
当主服务器进行命令传播时,它不只会将写命令发送给全部从服务器,还会将写命令入队到复制积压缓冲区,以下图所示:
因此,主服务器的复制积压缓冲区会保存着一部分最近传播的写命令,而且为队列中的每一个字节记录相应的复制偏移量,以下所示:
偏移量 | ... | 10087 | 10088 | 10089 | 10090 | 10091 | ... |
---|---|---|---|---|---|---|---|
字节值 | ... | '*' | 3 | '\r' | '\n' | '$' | ... |
当从服务器从新链接上主服务器时,会经过PSYNC
命令将本身的复制偏移量offset发送给主服务器,主服务器会根据如下规则来决定对从服务器执行何种同步操做:
回到以前的例子:
PSYNC
命令,报告本身的复制偏移量为10086。PSYNC
命令以及偏移量10086以后,会检查偏移量10086以后的数据是否存在于复制积压缓冲区,结果发现数据还在,因而主服务器向从服务器A发送+CONTINUE回复,表示数据同步将以部分重同步模式来进行。每一个Redis服务器,不论主服务器仍是从服务器,都会有本身的运行ID,运行ID在服务器启动时自动生成,由40个十六进制字符组成,以下图所示:
当从服务器对主服务器进行初次复制时,主服务器会将本身的运行ID传送给从服务器,从服务器会将这个运行ID保存起来。
当从服务器断线并从新链接上主服务器时,从服务器会把以前保存的运行ID发送给当前链接的主服务器:
对于从服务器来讲,调用PSYNC
命令有如下2种状况:
若是从服务器之前没有复制过任何主服务器,或者以前执行过SLAVEOF on one
命令,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1
命令,主动请求主服务器进行完整重同步。
若是从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送
PSYNC {runid} {offset}
命令,其中runid是上一次复制的主服务器的运行ID,offset是从服务器当前的复制偏移量。
对于主服务器来讲,接收到PSYNC
命令后会向从服务器返回如下3种回复中的一种:
+FULLRESYNC {runid} {offset}
,表示主服务器将与从服务器执行完整重同步操做,其中runid是主服务器的运行ID,从服务器会将这个ID保存起来,在下一次发送PSYNC
命令时使用,offset是主服务器当前的复制偏移量,从服务器会将这个值做为本身的初始化偏移量。+CONTINUE
,表示主服务器将与从服务器执行部分重同步操做,主服务器会将从服务器缺乏的那部分数据发送给从服务器。若是主服务器返回-ERROR,表示主服务器的版本低于Redis 2.8,它识别不了PSYNC命令,从服务器将向主服务器发送SYNC
命令,并与主服务器执行完整重同步操做。以上描述流程可使用如下流程图来表示:
黄健宏 《Redis设计与实现》