redis 之主从同步(两个缓冲区尤为重要)
当启动多个服务器时,通过replicaof(redis 5.0之前使用slaveof)命令形成主库和从库之间的关系
首先介绍一下名词 命令传播:即在主服务器数据库状态被修改,导致主从服务器数据库状态出现不一致时,让主从服务器的数据库状态重新回到一致状态。一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。
这里就要提到一些命令:在早期版本中,会使用sync进行主从数据库间数据同步,但是sync采用的是每次全量同步,即每次与数据库状态不一致时,主服务器都会生成完整rdb文件传输给从服务器,
这样带来的影响时,因为主服务器会执行bgsave ,创建子进程生成rdb文件,这个操作会耗费主服务器大量的cpu,内存和磁盘资源,主服务器同时需要将生成的rdb文件传递给从服务器,这个发送操作同样
会耗费主从服务器大量的网络带宽和流量,接收到rdb文件的从服务器需要载入主服务器发来的rdb我呢间,并且在载入期间,从服务器会因为阻塞而没有办法处理命令请求。
在新版本中采用了psync来进行主从同步,首先,主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,
主从库间就可以开始同步了。
下面介绍psync的命令参数:
下面所述皆为第一次进行同步时的情况:
从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。
runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。
当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。
offset,此时设为 -1,表示第一次复制。主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。
FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
在主从服务器进行同步的过程中,主库不会被阻塞,同样可以接受请求否则,Redis 的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。 (注意这里所说的同步是第一次,这里所说的缓冲区与下面要介绍的缓冲区并不是一样的,下面将要介绍,如果进行第一次同步后,主从服务器网络中断后,又重新进行恢复后(对于中间产生的数据造成主从库状态不一致)的应对策略)
最后主库会把执行过程中replication_buffer缓冲区中的的写命令,再发送给从库。具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作
到此在第一次进行全量复制时可能出现的各种讨论已经介绍完毕。
下面就要讨论,命令传播阶段出现的主从库状态不一致,和主从库间网络中断时出现的一些问题。
从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。增量复制只会把主从库网络断连期间主库收到的命令,同步给从库。
这里就会用到三个增量复制的实现细节:
主服务器的复制偏移量:replication_offset
主服务器的复制积压缓冲区:replication_backlog
服务器的运行id
对于复制偏移量:执行复制的双方,主从服务器都会维护一个复制偏移量,主服务器每次向从服务器传播n个字节的数据时,就将自己的肤质偏移量的值加上N,从服务器每次收到主服务器传播来的N个字节数据时,就将自己的复制偏移量的值加上N。
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。
即同时主服务器进行命令传播期间(从服务器完成全量复制后,主服务器接受到写命令,会同时将命令传播到从服务器),不仅将写命令发送给所有服务器,还会将写命令写入复制积压缓冲区。
主服务器的复制积压缓冲区会保存着一部分最近传输的命令。
所以如果网络中断后,主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距。
在网络断连阶段,主库可能会收到新的写操作命令,所以,一般来说,master_repl_offset 会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库就行。
比较容易弄混的一个地方 replication_buffer 与 replication_backlog_buffer 之间的区别
首先来说一下复制缓冲区。
作用:主节点开始和一个从节点进行全量同步时,会为从节点创建一个输出缓冲区,这个缓冲区就是复制缓冲区。当主节点向从节点发送 RDB 文件时,如果又接收到了写命令操作,就会把它们暂存在复制缓冲区中。等 RDB 文件传输完成,并且在从节点加载完成后,主节点再把复制缓冲区中的写命令发给从节点,进行同步。对主从同步的影响:如果主库传输 RDB 文件以及从库加载 RDB 文件耗时长,同时主库接收的写命令操作较多,就会导致复制缓冲区被写满而溢出。一旦溢出,主库就会关闭和从库的网络连接,重新开始全量同步。所以,我们可以通过调整 client-output-buffer-limit slave 这个配置项,来增加复制缓冲区的大小,以免复制缓冲区溢出。
再来看看复制积压缓冲区。
作用:主节点和从节点进行常规同步时,会把写命令也暂存在复制积压缓冲区中。如果从节点和主节点间发生了网络断连,等从节点再次连接后,可以从复制积压缓冲区中同步尚未复制的命令操作。对主从同步的影响:如果从节点和主节点间的网络断连时间过长,复制积压缓冲区可能被新写入的命令覆盖。此时,从节点就没有办法和主节点进行增量复制了,而是只能进行全量复制。针对这个问题,应对的方法是调大复制积压缓冲区的大小