数据同步:主从库如何实现数据一致?

168 阅读6分钟

Redis的高可靠性?

redis的高可靠性主要是下面两点来保证:

  1. 使用AOF和RDB保证数据的持久性,数据尽量少丢失。
  2. 服务尽量少中断:增加副本冗余量,将一份数据同时保存在多个实例上。

🍊多副本之间的数据如何保证一致性?

Redis提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。

  • 读操作:主库、从库都可以接收;
  • 写操作:首先到主库执行,然后,主库将写操作同步给从库。

image.png

为什么设计成读写分离?

  1. 如果要保持这个数据在三个实例上一致,就要涉及到加锁、实例间协商是否完成修改等一系列操作,但这会带来巨额的开销,当然是不太能接受的。
  2. 而主从库模式一旦采用了读写分离,所有数据的修改只会在主库上进行,不用协调三个实例。主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。

🍊主从库间如何进行同步?

当我们启动多个Redis实例的时候,它们相互之间就可以通过replicaof(Redis 5.0之前使用slaveof)命令形成主库和从库的关系,从库从主库上复制数据。

image.png

  1. FULLRESYNC响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。具体来说,主库执行bgsave命令,生成RDB文件,接着将文件发给从库。从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件。这是因为从库在通过replicaof命令开始和主库同步前,可能保存了其他数据。为了避免之前数据的影响,从库需要先把当前数据库清空。
  2. 在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。为了保证主从库的数据一致性,主库会在内存中用专门的replication buffer,记录RDB文件生成后收到的所有写操作。

🍎可以采用主从级联分担全量复制的主库压力

目前存在的问题

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

image.png

🍓主从间网络断了怎么办?

在Redis 2.8之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。 从Redis 2.8开始,网络断了之后,主从库会采用增量复制的方式继续同步。听名字大概就可以猜到它和全量复制的不同:全量复制是同步所有数据,而增量复制只会把主从库网络断连期间主库收到的命令,同步给从库。 当主从库断连后,主库会把断连期间收到的写操作命令,写入replication buffer,同时也会把这些操作命令也写入repl_backlog_buffer这个缓冲区。

replication buffer和repl_backlog_buffer的区别?

  • replication buffer:是主从库在进⾏全量复制时,主库上⽤于和从库连接的客⼾端的buffer,接收全量复制期间的命令。当主库要把全量复制期间的写操作命令发给从库时,主库会先创建⼀个客⼾端,⽤来连接从库,然后通过这个客⼾端,把写操作命令发给每一个从库。所以replication buffer不是共享的,⽽是每个从库都有⼀个对应的客⼾端。
  • repl_backlog_buffer:环形缓存区,记录两个offset(master_repl_offset:主库写的位置,slave_repl_offset从库同步数据的位置),在Redis服务器启动后,开始⼀直接收写操作命令,这是所有从库共享的。主库和从库会各⾃记录⾃⼰的复制进度,所以,不同的从库在进⾏恢复时,会把⾃⼰的复制进度(slave_repl_offset)发给主库,主库就可以和它独⽴同步。

image.png

由于repl_backlog_buffer是环形的,如果写入速度大于读取速度,那么就可能会出现数据覆盖的情况。

为此我们需要设置合理的repl_backlog_size,缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即repl_backlog_size = 缓冲空间大小 * 2。

image.png

主库挂了怎么办?

在主从模式下,如果从库发⽣故障了,客⼾端可以继续向主库或其他从库发送请求,进⾏相关的操作,但是如果主库发⽣故障了,那就直接会影响到从库的同步,因为从库没有相应的主库可以进⾏数据复制操作了。

哨兵机制

哨兵其实就是⼀个运⾏在特殊模式下的Redis进程,主从库实例运⾏的同时,它也在运⾏。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。

image.png

  • 监控是指哨兵进程在运⾏时,周期性地给所有的主从库发送PING命令,检测它们是否仍然在线运⾏。如果没有在规定时间内响应哨兵的PING命令,哨兵就会把它标记为“下线状态”,如果是主库被标记,开始⾃动切换主库的流程。
  • 主库挂了以后,哨兵就需要从很多个从库⾥,按照⼀定的规则选择⼀个从库实例,把它作为新的主库。这⼀步完成后,现在的集群⾥就有了新主库。
  • 哨兵会把新主库的连接信息通知给客⼾端,让它们把请求操作发到新主库上。

存在的两个问题

🍉 主库真的挂了吗?

  • 如果检测的是从库,那么,哨兵简单地把它标记为“主观下线”就⾏了。(从库对哨兵的PING命令没有及时响应)
  • 对于主库:哨兵通常会采⽤多实例组成的集群模式进⾏部署,这也被称为哨兵集群。引⼊多个哨兵实例⼀起来判断,就可以避免单个哨兵因为⾃⾝⽹络状况不好,⽽误判主库下线的情况。

🍊 该选择哪个从库作为主库?

⼀般来说,我把哨兵选择新主库的过程称为“筛选+打分”。简单来说,我们在多个从库中,先按照⼀定的筛选条件(从库优先级、从库复制进度以及从库ID号),把不符合条件的从库去掉。然后,我们再按照⼀定的规则,给剩下的从库逐个打分,将得分最⾼的从库选为新主库,如下图所⽰:

image.png