Redis如何实现主从复制

1.Redis的高可靠性

Redis的高可靠性:

  • 数据尽量少丢失。AOF和RDB可以分别通过回放日志和重新读入RDB文件的方式恢复数据。
  • 服务尽量少中断。增加Redis的实例副本。将一份数据同时保存在多个实例上,即使有一个实例出现了故障,需要过一段时间恢复,其他实例也能对外提供服务,不会影响业务使用。

2.Redis如何实现多实例之间的数据一致

Redis提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。
读操作:主库、从库都可以接收。
写操作:首先到主库执行,然后,主库将写操作同步给从库。
Redis如何实现主从复制

3.为什么主从库需要采用读写分离方式呢

如果主库和从库都能接收客户端的写操作,如果不加锁的情况下,那么多次请求可能会被分发到不同的数据库里,实例上的副本可能会不一致。
而如果加锁了,就会带来巨大的开销。

而主从库模式一旦采用了读写分离,所有的数据的修改都只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。

4.主从复制是如何完成的呢?

当我们启动多个Redis实例的时候,他们相互之间可以通过relicaof(5.0之前使用的是slaveof)命令形成主库和从库的关系,之后会按照三个阶段来完成数据的第一次同步。

Redis如何实现主从复制
**第一阶段:**是主从库建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库就可以开始同步了。

具体:从库会给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包括询问主库的runID和复制进度offset两个参数。

  • runID,是每个Redis实例启动时都会自动生成的一个随机ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的runID,所以将runID设为?。
  • offset:此时设为-1,表示第一次复制。

主库收到psync命令后,会用FULLRESYNC相应命令带上两个参数:主库runID和主库目前的复制进度offset,返回给从库。从库收到响应后,会记录下这两个参数。

这里有个地方需要注意,FULLRESYNC响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。

第二阶段::主库将所有数据同步给从库,从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照生成的RDB文件。

具体来说,主库执行BGSAVE命令,生成RDB文件,接着将文件发给从库。从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件。这是因为从库在通过relicaof命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。

在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis的服务就被中断了。但是这些请求中的写操作并没有记录到刚刚生成的RDB文件中。为了保证主从库的数据一致性,主库会在内存中用专门的repication buffer,记录RDB文件生成后收到的所有写操作。

**第三阶段:**主库会把第二阶段执行过程中新收到的写命令,再发送给从库。

具体的锁,就是当主库完成RDB文件发送后,就会把此时replication buffer中的修改操作发送给从库,从库再重新执行这些操作。这样一来,朱从库就实现同步了。

5.主从级联模式

可以看到,在一次全量复制过程中,对于主库需要完成两个耗时的操作:生成RDB文件和传输RDB文件。

如果从库数量很多,而且都需要和主库进行全量复制的话,就会导致主库忙于fork子进程生成RDB文件,进行数据全量同步。fork这个操作会阻塞主线程处理正常的请求,从而导致主库响应应用程序的请求速度变慢。此外,传输RDB文件也会占用主库的网络带宽,同样会给主库的资源使用带来压力。

所以可以采用主-从-从级联模式来分担主库全量复制时的压力,以级联的方式分散到从库上。

也就是说,我们在部署集群的时候,可以手动选择从库(比如选择内存资源配置较高的从库)用于级联其他的从库。

然后,我们可以再选择一些从库,在这些从库上上执行replicaof命令,让他们和刚才所选的从库,建立起主从关系。

这样,这些从库就会知道,在进行同步时,不用再和主库进行交互了,只要和级联的从库进行写操作同步就行了,这就可以减轻主库上的压力。

Redis如何实现主从复制
一旦主从库完成了全量复制,他们就会一直维护一个网络连接,主库就会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。

6.主从库网络段了怎么办

Redis2.8之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。

从Redis2.8开始,网络断了之后,主从库会采用增量复制的方式继续同步。

全量复制:同步所有数据。
增量复制:只会复制主从库网络断连期间主库收到的命令,同步给从库。

7.增量复制如何保证同步呢

主从库段连后,主库会把断连期间收到的写操作命令,写入replication buffer,同时也会把这些操作命令也写入repl_backlog_buffer这个缓冲区。

repl_backlog_buffer是一个环形缓冲区。主库会记录自己写到的位置,从库则会记录自己已经读到的位置。

刚开始,主库和从库的写读位置会在一起,这算它们的起始位置。随着主库不断接收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置,我们通常用偏移量来衡量这个偏移距离的大小,对主库来首,对应的偏移量就是master_repl_offset。主库接收新的写操作越多,这个值也就会越大。

同样,从库在复制完写操作后,它在缓冲区的读位置也会逐步开始偏移刚才的起始位置。此时,从库已复制的偏移量slave_repl_offset也在不断增加。正常情况下,这两个偏移量基本相等。

Redis如何实现主从复制
主库的连接恢复之后,从库会首先给主库发送psync命令,并把自己当前的slave_repl_offset发给从库,主库会判断自己的master_repl_offset之间的差距。

在网络断连节点,主库可能会收到新的写操作命令,所以一般来首master_repl_offset会大于slave_repl_offset。此时,主库只用把master_repl_offset和slave_repl_offset之间的命令操作同步给从库就行。

Redis如何实现主从复制
注意:
repl_backlog_buffer是一个环形缓冲区,所以在缓冲区写满之后,如果主库继续写入,就会覆盖之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。

解决方案:
我们可以调整repl_backlog_size这个参数。
可以将缓存空间大小 = 主库写入命令的速度 * 操作大小 - 主从库网络传输命令速度 * 操作大小。
在实际应用中还需一把缓冲空间扩大一倍。
repl_backlog_size = 缓冲空间大小 * 2

举个例子,如果主库每秒写入 2000 个操作,每个操作的大小为 2KB,网络每秒能传输 1000 个操作,那么,有 1000 个操作需要缓冲起来,这就至少需要 2MB 的缓冲空间。否则,新写的命令就会覆盖掉旧操作了。为了应对可能的突发压力,我们最终把 repl_backlog_size 设为 4MB。

这样一来,增量复制时主从库的数据不一致的风险就降低了。不过如果并发请求量非常大,这两倍的缓冲空间都存不下新操作请求的话,还是有可能导致主从库数据不一致。

这种情况下:

  1. 可以根据Redis所在服务器的内存资源再适当增加repl_backlog_size值,比如设置成缓冲空间大小的4倍。
  2. 可以考虑使用切片集群来分担单个主库的请求压力。

总结与思考:
主从库模式使用读写分离虽然避免了同时写多个实例带来的数据不一致问题,但是还是面临着主库的潜在风险。
如果主库故障了怎么办?
还能保证数据一致吗?
Redis还能正常提供服务吗?

所以要采用哨兵机制对主库的情况进行监控。

上一篇:Redis:一文带你了解RDB


下一篇:Blog.071 NoSQL Redis 配置与优化