6.2 主从复制

2016-03-20 02:09:03 3,776 0

通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据。但是由于数据是存储在一台服务器上的,如果这台服务器的硬盘出现故障,也会导致数据丢失。为了避免单点故障,我们希望将数据库复制多个副本以部署在不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。这就要求当一台服务器上的数据库更新后,可以自动将更新的数据同步到其他服务器上,Redis提供了复制(replication)功能可以自动实现同步的过程。

6.2.1 配置

同步后的数据库分为两类,一类是主数据库(master),一类是从数据库(slave)。主数据库可以进行读写操作,当发生写操作时自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库,如图7-1所示。

QQ截图20160318221047.png

在Redis中使用复制功能非常容易,只需要在从数据库的配置文件中加入“slaveof主数据库IP主数据库端口”即可,主数据库无需进行任何配置。

为了能够更直观地展示复制的流程,下面将进行简单的演示。我们要在一台服务器上启动两个Redis实例,监听不同端口,其中一个作为主数据库,另一个作为从数据库。首先我们不加任何参数来启动一个Redis实例作为主数据库:

$redis-server

该实例默认监听6379端口。然后加上slaveof参数启动另一个Redis实例作为从数据库,并让其监听6380端口:

redis-server --port 6380 --slaveof 127.0.0.1 6379

此时在主数据库中的任何数据变化都会自动同步到从数据库中。我们打开redis-cli实例A并连接到主数据库:

redis-cli

再打开redis-cli实例B并连接到从数据库:

redis-cli -p 6380

在实例A中使用SET命令设置一个键的值:

redis A>SET foo bar
OK

此时在实例B中就可以获得该值了:

redis B>GET foo
"bar"

但在默认情况下从数据库是只读的,如果直接修改从数据库的数据会出现错误:

redis B>SET foo hi
(error) READONLY You can't write against a read only slave.

可以通过设置从数据库的配置文件中的slave-read-only为no以使从数据库可写,但是对从数据库的任何更改都不会同步给任何其他数据库,并且一旦主数据库中更新了对应的数据就会覆盖从数据库中的改动。

配置多台从数据库的方法也一样,在所有的从数据库的配置文件中都加上 slaveof参数指向同一个主数据库即可。

除了通过配置文件或命令行参数设置slaveof参数,还可以在运行时使用SLAVEOF命令修改:

redis>SLAVEOF 127.0.0.1 6379

果该数据库已经是其他主数据库的从数据库了,SLAVEOF命令会停止和原来数据库的同步转而和新数据库同步。还可以使用SLAVEOF NO ONE来使当前数据库停止接收其他数据库的同步转成主数据库。


6.2.2 原理

了解Redis复制的原理对日后运维有很大的帮助。

当一个从数据库启动后,会向主数据库发送SYNC命令,主数据库接收到SYNC命令后会开始在后台保存快照(即RDB持久化的过程),并将保存期间接收到的命令缓存起来。当快照完成后,Redis会将快照文件和所有缓存的命令发送给从数据库。从数据库收到后,会载入快照文件并执行收到的缓存的命令。当主从数据库断开重连后会重新执行上述操作,不支持断点续传。

实际的过程略微复杂一些,由于Redis服务器使用TCP协议通信,所以我们可以使用telnet工具伪装成一个从数据库来了解同步的具体过程。首先在命令行中连接主数据库(默认端口为6379,且没有任何从数据库连接):

telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

然后作为从数据库,我们先要发送PING命令确认主数据库是否可以连接:

PING
+PONG

主数据库会回复+PONG。如果没有收到主数据库的回复,则向用户提示错误。如果主数据库需要密码才能连接,我们还得发送AUTH命令进行验证(关于Redis的安全设置会在7.3节介绍)。而后向主数据库发送REPLCONF命令说明自己的端口号(这里随便选择了一个):

REPLCONF listening-port 6381
+OK

这时就可以开始同步的过程了:向主数据库发送SYNC命令开始同步,此时主数据库发送回快照文件和缓存的命令。目前主数据库中只有一个foo键,所以收到的内容如下(快照文件是二进制格式,从第三行开始):

SYNC
$29
REDIS0006?foobar?6_?"

从数据库会将收到的内容写入到硬盘上的临时文件中,当写入完成后从数据库会用该临时文件替换RDB快照文件(RDB快照文件的位置就是持久化时配置的位置,由dir和dbfilename两个参数确定),之后的操作就和RDB持久化时启动恢复的过程一样了。需要注意的是在同步的过程中从数据库并不会阻塞,而是可以继续处理客户端发来的命令。默认情况下,从数据库会用同步前的数据对命令进行响应。可以配置slave-serve-stale-data参数为no来使从数据库在同步完成前对所有命令(除了INFO和SLAVEOF)都回复错误:“SYNC with master in progress.”

之后主数据库的任何数据变化都会同步给从数据库,同步的内容和Redis通信协议一样,比如我们在主数据库中执行SET foo hi,通过telnet我们收到了:

*3
$3
set
$3
foo
$2
hi

在复制的过程中,快照无论在主数据库还是从数据库中都起了很大的作用,只要执行复制就会进行快照,即使我们关闭了RDB方式的持久化(通过删除所有save参数)。更进一步,无论是否启用了RDB方式的持久化,Redis在启动时都会尝试读取dir和dbfilename两个参数指定的RDB文件来恢复数据库。

6.2.3 图结构

从数据库不仅可以接收主数据库的同步数据,自己也可以同时作为主数据库存在,形成类似图的结构,如图7-2所示,数据库A的数据会同步到B和C中,而B中的数据会同步到D和E中。向B中写入数据不会同步到A或C中,只会同步到D和E中。

QQ截图20160318221958.png

6.2.4 读写分离

通过复制可以实现读写分离以提高服务器的负载能力。在常见的场景中,读的频率大于写,当单机的Redis无法应付大量的读请求时(尤其是较耗资源的请求,比如SORT命令等)可以通过复制功能建立多个从数据库,主数据库只进行写操作,而从数据库负责读操作。

6.2.5 从数据库持久化

另一个相对耗时的操作是持久化,为了提高性能,可以通过复制功能建立一个(或若干个)从数据库,并在从数据库中启用持久化,同时在主数据库禁用持久化。当从数据库崩溃时重启后主数据库会自动将数据同步过来,所以无需担心数据丢失。而当主数据库崩溃时,需要在从数据库中使用SLAVEOF NO ONE命令将从数据库提升成主数据库继续服务,并在原来的主数据库启动后使用SLAVEOF命令将其设置成新的主数据库的从数据库,即可将数据同步回来。

6.2.6 总结

Redis主从复制的配置十分简单,它可以使slave是master的完全拷贝。下面是关于Redis主从复制的几点重要内容:

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

  2. 一个master可以有多个slave

  3. slave也可以接受其他slave的连接。除了多个slave连接到一个master之外,多个slave也可以连接到一个slave上,形成一个图状结构

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

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

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

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


当主服务器不进行持久化时复制的安全性

在进行主从复制设置时,强烈建议在master服务器上开启持久化,当不能这么做时,比如考虑到延迟的问题,应该将实例配置为避免自动重启。

为什么不持久化的master服务器自动重启非常危险呢?为了更好的理解这个问题,看下面这个失败的例子,其中master服务器和slave服务器中数据库都被删除了。

我们设置节点A为master,关闭持久化,节点B和C从节点A复制数据。

这时出现了一个崩溃,但Redis具有自动重启系统,重启了进程,因为关闭了持久化,节点重启后只有一个空的数据集。

节点B和C从节点A进行复制,现在节点A是空的,所以节点B和C上的复制数据也会被删除。

当在高可用系统中使用Redis Sentinel,关闭了master服务器的持久化,并且允许自动重启,这种情况是很危险的。比如master服务器可能在很短的时间就完成了重启,以至于Sentinel都无法检测到这次失败,那么上面说的这种失败的情况就发生了。

如果数据比较重要,并且在使用主从复制时关闭了主服务器持久化功能的场景中,都应该禁止实例自动重启。

部分重新同步

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

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

部分重新同步这个新特性内部使用PSYNC命令,旧的实现中使用SYNC命令。Redis2.8版本可以检测出它所连接的服务器是否支持PSYNC命令,不支持的话使用SYNC命令。

无磁盘复制

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

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

这一特性目前只是实验性的。

关于部分重新同步,还有一些针对复制内存缓冲区的优化参数。查看Redis介质中的Redis.conf示例获得更多信息。

使用repl-diskless-sync配置参数来启动无磁盘复制。使用repl-diskless-sync-delay 参数来配置传输开始的延迟时间,以便等待更多的从服务器连接上来。

设置从服务器到主服务器验证

如果主服务器设置了密码,配置从服务器在所有同步中使用这个密码十分简单。

对于运行中的实例,使用redis-cli并输入:

config set masterauth <password>

要使配置永久生效,把如下命令加入到配置文件中:

masterauth <password>

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

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

这一特性的工作原理如下:

  • 从服务器每秒钟ping一次主服务器,确认处理的复制流数量。

  • 主服务器记住每个从服务器最近一次ping的时间。

  • 用户可以配置最少要有N个服务器有小于M秒的确认延迟。

  • 如果有N个以上从服务器,并且确认延迟小于M秒,主服务器接受写操作。

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

如果条件不满足,主服务器会拒绝写操作并返回一个错误。

min-slaves-to-write(最小从服务器数)

min-slaves-max-lag(从服务器最大确认延迟)


参考文档:

http://ifeve.com/redis-replication/

上一篇:6.1 持久化 下一篇:6.3 redis-sentinel