Redis主从一致性保证

前言

Redis之所以牛逼,一方面是因为他快,另一方面是因为高可靠性。所谓的高可靠性就是指,尽量的少丢数据,和服务端尽量少中断。AOF和RDB保证了前者,但是不能保证后者。后者是根据什么保证的呢?

Redis 的做法就是增加副本冗余量,将一份数据同时保存在多个实例上。即使有一个实例出现了故障,需要过一段时间才能恢复,其他实例也可以对外提供服务,不会影响业务使用。

这里就会产生主从数据一致性的问题。
在这里插入图片描述

主从同步过程

在这里插入图片描述
两个重要参数
runID,实例唯一标志。是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,不知道主库的 runID,所以可以将 runID 设为“?”。
offset,表示复制进度。如果是第一次复制,可以设为 -1。

第一阶段

从库给主库发送 psync ?-1命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。

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

  • FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。

第二阶段

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

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

第三阶段

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

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

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

主从库间网络断连后的解决办法

在 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。
从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。

增量复制时,主从库之间具体是怎么保持同步的呢?

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

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

当主从库断连后,主库会把断连期间收到的写操作命令写入 repl_backlog_buffer 这个缓冲区,主库偏移量 master_repl_offset,从库偏移量slave_repl_offset

在从库重连之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offsetslave_repl_offset 之间的差距。
在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于slave_repl_offset。此时,主库只用把 master_repl_offsetslave_repl_offset 之间的命令操作同步给从库就行。

在这里插入图片描述

注意事项

因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。
既然有些未读取的操作被覆盖了,那这时候从库该怎么办?这时候主从库之间将进行全量复制。

如何避免未读取的数据被覆盖

缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小

在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即 repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。

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

这样增量复制时主从库的数据不一致风险就降低了。但在并发请求量非常大的情况下,仍有缓冲空间不够的情况,主从库数据还是可能不一致。
对于这种情况,一方面可以根据Redis 所在服务器的内存资源再适当增加 repl_backlog_size 值;另一方面,可以通过切片集群的方法来分担单个主库的请求压力。

总结

本文主要是研究了Redis 的主从库同步的基本原理,关于全量复制、基于长连接的命令传播,以及增量复制。
全量复制虽然耗时,但是对于从库来说,如果是第一次同步,全量复制是无法避免的
长连接复制是主从库正常运行后的常规同步阶段。在这个阶段中,主从库之间通过命令传播实现同步
增量复制在主从库断连的情况比较有效

猜你喜欢

转载自blog.csdn.net/yzx3105/article/details/129838441