一篇让你了解Redis同步--详解全部

目录

一、主从架构数据同步原理

1. 主从复制概述

2.搭建主从服务架构

3.主从复制原理

3.1同步的三种情况

5. 主从应用问题

5.1 Redis 有两种删除策略:

5.2 单机内存大小限制

6.总结

二、Redis主从同步与故障切换会遇到的问题

1.主从数据不一致

1.1例子

1.2主要原因:

1.3从库会滞后执行同步命令原因

1.4解决方式

2.读取过期数据

2.1客户端可以读取过期数据原因

3.部分总结

4.不合理配置项导致的服务挂掉

1.protected-mode配置项

2.cluster-node-timeout配置项

5.总结

关于主从库数据不一致的问题

lave-serve-stale-data配置项和 slave-read-only 的区别


Redis的高可靠性

Redis的高可靠性:1:保证数据尽量少丢失;(AOF和RDB保证了数据尽少丢失)2:服务尽量少中断。(而服务尽量少中断就需要通过增加冗余的方式来实现,将一份数据保存在多个实例。一个实例发生故障,恢复需要时间,其余的实例也可以使用不需要等待)

什么是高可用?

高可用:数据尽量不丢失,尽量提供所有服务。

一、主从架构数据同步原理

Redis宕机了,可以使用AOF或者RDB将数据进行恢复,通过回放日志和

Redis宕机了,怎么实现高可用?

Redis提供了主从模式,主节点,从节点通过主从复制,将数据备份冗余到其他服务器上

1. 主从复制概述

作用:

  1. 故障修复:主节点宕机的时候,其他节点依然可以提供服务;
  2. 负载均衡:主节点提供写服务,从节点提供读服务,分担压力;
  3. 高可用的实现:是哨兵和cluster实施的基础

主从怎么保持一致性?

Redis提供了主从库的形式,来保持数据副本的一致性,主从库之间采用读写分离的方式

采用的是读写分离的方式

读操作:主、从库都可以执行;

写操作:主库先执行完,在同步到字库中。

读写分离的目的?

为了避免写操作的时候主从服务器都在执行,每次修改都发送到主从实例上,就会造成实例副本数据不一致的现象。

你可以设想一下,如果在上图中,不管是主库还是从库,都能接收客户端的写操作,那么,一个直接的问题就是:如果客户端对同一个数据(例如k1)前后修改了三次,每一次的修改请求都发送到不同的实例上,在

不同的实例上执行,那么,这个数据在这三个实例上的副本就不一致了(分别是v1、v2和v3)。在读取这个数据的时候,就可能读取到旧的值。

如果要使得她们的数据一样就必须要加锁,实例间需要协商是否修改,加锁就会产生额外的开销。

只有主库可以写,同步到从库中,不需要协商,数据保持一致。

2.搭建主从服务架构

3.主从复制原理

主库先执行写操作,然后在同步到从库,保证数据的一致性。

3.1同步的三种情况

  • 第一次主从库全量复制
  • 主从正常运行期间的同步
  • 主从库间网络断开同步

3.1.1第一次主从库全量复制

第一次复制过程主要分为三个阶段:连接建立阶段(即准备阶段),主库同步数据到从库阶段,发送同步阶段新写命令到从库阶段

例如:现在有实例1(ip:172.16.19.3)和实例2(ip:172.16.19.5),我们在实例2上执行以下命令,让实例2变成实例1的从库,并且复制数据:

replicaof 172.16.19.3 6379

建立连接
 

第一阶段

该阶段的主要作用是在主从节点之间建立连接,为数据全量同步做好准备。从库会和主库建立连接,从库(执行 replicaof)给主库 发送 psync 命令并告诉主库即将进行同步,主库确认回复后,主从库间就开始同步了。 (第一阶段是主从库间建立连接、协商同步的过程,主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。)

从库怎么知道主库信息并且建立连接呢?

在从节点的配置文件中的 replicaof 配置项中配置了主节点的 IP 和 port 后,从节点就知道自己要和那个主节点进行连接了。

从节点内部维护了两个字段,masterhost 和 masterport,用于存储主节点的 IP 和 port 信息。

从库(执行 replicaof)给主库发送 psync 命令,表示要执行数据同步,主库收到命令后根据参数启动复制。命令包含了主库的 runID复制进度 offset 两个参数。

  • runID:每个 Redis 实例启动都会自动生成一个 唯一标识 ID,第一次主从复制,还不知道主库 runID,参数设置为 「?」。
  • offset:第一次复制设置为 -1,表示第一次复制,记录复制进度偏移量。

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

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

主库同步数据给从库

为什么使用RDB不使用AOF文件?

AOP文件比RDB文件大,网络传输比较耗时。

在从库初始化数据时,RDB比AOF文件执行的更快

第二阶段

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

master 执行 bgsave命令生成 RDB 文件,并将文件发送给从库,同时主库为每一个 slave 开辟一块 replication buffer 缓冲区记录从生成 RDB 文件开始收到的所有写命令。

从库收到 RDB 文件后保存到磁盘,并清空当前数据库的数据,再加载 RDB 文件数据到内存中。(先存磁盘,在清除内存数据,最后加载rdb进入内存)

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

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

发送新写命令到从库

第三阶段

从节点加载 RDB 完成后,主节点将 replication buffer 缓冲区的数据发送到从节点,Slave 接收并执行,从节点同步至主节点相同的状态。

最后,也就是第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成RDB文件发送后,就会把此时replication buffer中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。

正常接收请求

在生成 RDB 文件之后的写操作并没有记录到刚刚的 RDB 文件中,为了保证主从库数据的一致性,所以主库会在内存中使用一个叫 replication buffer 记录 RDB 文件生成后的所有写操作。

从库收到 RDB 文件后要清空当前数据库?

因为从库在通过 replicaof命令开始和主库同步前可能保存了其他数据,防止主从数据之间的影响。

replication buffer解释?

一个在 master 端上创建的缓冲区,存放的数据是下面三个时间内所有的 master 数据写操作。

1)master 执行 bgsave 产生 RDB 的期间的写操作;

2)master 发送 rdb 到 slave 网络传输期间的写操作;

3)slave load rdb 文件把数据恢复到内存的期间的写操作。

每个 client (客户端通信,从库通信)连上 Redis 后,Redis 都会分配一个专有 client buffer,所有数据交互都是通过这个 buffer 进行的。(Master 先把数据写到这个 buffer 中,然后再通过网络发送出去,这样就完成了数据交互)。buffer 专门用来传播写命令到从库,保证主从数据一致,我们通常把它叫做 replication buffer。


replication buffer 太小会引发的问题:

replication buffer 由 client-output-buffer-limit slave 设置,当这个值太小会导致主从复制连接断开。(replication buffer 由 client-output-buffer-limit slave 设置,当这个值太小会导致主从复制连接断开。)

主从复制连接断开引发的问题:

config set client-output-buffer-limit "slave 536870912 536870912 0"

1)当 master-slave 复制连接断开,master 会释放连接相关的数据。replication buffer 中的数据也就丢失了,此时主从之间重新开始复制过程。

2)还有个更严重的问题,主从复制连接断开,导致主从上出现重新执行 bgsave 和 rdb 重传操作无限循环。

当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;

主节点数据量较大引发的问题:(主从连接断开。无限循环)

这种情况可能引起全量复制 -> replication buffer 溢出导致连接中断 -> 重连 -> 全量复制 -> replication buffer 缓冲区溢出导致连接中断……的循环。

主从库复制为何不使用 AOF 呢?相比 RDB 来说,丢失的数据更少

  1. RDB 文件是二进制文件,网络传输 RDB 和写入磁盘的 IO 效率都要比 AOF 高。
  2. 从库进行数据恢复的时候,RDB 的恢复效率也要高于 AOF。

3.1.2第二种:增量复制(网络断开的同步)

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

2.8之前,断开重新进行一次全量复制,开销大。2.8之后,断开重连,主从采用增量的方式继续同步。

全量复制:同步所有数据

增量复制:用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效

当主从库断连后,主库会把断连期间收到的写操作命令,写入replication buffer,同时也会把这些操作命令

也写入repl_backlog_buffer这个缓冲区。

增量使用的缓存区

repl_backlog_buffer

主库:记录自己写到的位置

从库:记录自己读到的位置

断开重连增量复制的实现奥秘就是 repl_backlog_buffer 缓冲区,不管在什么时候 master 都会将写指令操作记录在 repl_backlog_buffer 中,因为内存有限, repl_backlog_buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容

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

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

master 使用 master_repl_offset记录自己写到的位置偏移量,slave 则使用 slave_repl_offset记录已经读取到的偏移量。

master 收到写操作,偏移量则会增加。从库持续执行同步的写指令后,slave_repl_offset 也在不断增加。

正常情况下,这两个偏移量基本相等。在网络断连阶段,主库可能会收到新的写操作命令,所以 master_repl_offset会大于 slave_repl_offset。

当主从断开重连后,slave 会先发送 psync 命令给 master,同时将自己的 runID,slave_repl_offset发送给 master。主库会判断自己的master_repl_offset和slave_repl_offset之间的差距。

此时,master 只需要把 master_repl_offset与 slave_repl_offset之间的命令同步给从库即可。

就像刚刚示意图的中间部分,主库和从库之间相差了put d e和put d f两个操作,在增量复制时,主库只需要把它们同步给从库,就行了。

不过,有一个地方我要强调一下,因为repl_backlog_buffer是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。

repl_backlog_buffer扩容:

repl_backlog_buffer 太小的话从库还没读取到就被 Master 的新写操作覆盖了咋办?

我们要想办法避免这个情况,一旦被覆盖就会执行全量复制。我们可以调整 repl_backlog_size 这个参数用于控制缓冲区大小。计算公式:

repl_backlog_buffer = second * write_size_per_second
  1. second:从服务器断开重连主服务器所需的平均时间;
  2. write_size_per_second:master 平均每秒产生的命令数据量大小(写命令和数据大小总和);

例如,如果主服务器平均每秒产生 1 MB 的写数据,而从服务器断线之后平均要 5 秒才能重新连接上主服务器,那么复制积压缓冲区的大小就不能低于 5 MB。

为了安全起见,可以将复制积压缓冲区的大小设为2 * second * write_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理。

repl_backlog_buffer =2 * second * write_size_per_second

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

针对这种情况,一方面,你可以根据Redis所在服务器的内存资源再适当增加repl_backlog_size值,比如说设置成缓冲空间大小的4倍,另一方面,你可以考虑使用切片集群来分担单个主库的请求压力。

如何确定执行全量同步还是部分同步?

在 Redis 2.8 及以后,从节点可以发送 psync 命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制部分复制

全量复制还是增量复制判断:

关键就是 psync的执行:

  1. 从节点根据当前状态,发送 psync命令给 master:
    • 如果从节点从未执行过 replicaof ,则从节点发送 psync ? -1,向主节点发送全量复制请求;
    • 如果从节点之前执行过 replicaof 则发送 psync <runID> <offset>, runID 是上次复制保存的主节点 runID,offset 是上次复制截至时从节点保存的复制偏移量。
  1. 主节点根据接受到的psync命令和当前服务器状态,决定执行全量复制还是部分复制:
    • runID 与从节点发送的 runID 相同,且从节点发送的 slave_repl_offset之后的数据在 repl_backlog_buffer缓冲区中都存在,则回复 CONTINUE,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;
    • runID 与从节点发送的 runID 不同,或者从节点发送的 slave_repl_offset 之后的数据已不在主节点的 repl_backlog_buffer缓冲区中 (在队列中被挤出了),则回复从节点 FULLRESYNC <runid> <offset>,表示要进行全量复制,其中 runID 表示主节点当前的 runID,offset 表示主节点当前的 offset,从节点保存这两个值,以备使用。

一个从库如果和主库断连时间过长,造成它在主库 repl_backlog_buffer的 slave_repl_offset 位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制。

3.1.3.第三种:基于长连接的命令传播

完成全量同步后,正常运行过程如何同步呢

基于长连接的命令传播: 当主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库的过程

目的:使用长连接的目的就是避免频繁建立连接导致的开销。

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING 和 REPLCONF ACK。

主从级联模式分担全量复制时的主库压力

通过分析主从库间第一次数据同步的过程,你可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成RDB文件和传输RDB文件。

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

其实是有的,这就是“主-从-从”模式。

在刚才介绍的主从库模式中,所有的从库都是和主库连接,所有的全量复制也都是和主库进行的。现在,我们可以通过“主-从-从”模式将主库生成RDB和传输RDB的压力,以级联的方式分散到从库上。

简单来说,我们在部署主从集群的时候,可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后,我们可以再选择一些从库(例如三分之一的从库),在这些从库上执行如下命令,让它们和刚才所选的从库,建立起主从关系。

replicaof   所选的从库ip   port

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

心跳机制:(检查网络是否断开)

主->从:PING

每隔一段时间主库会向从库发送ping命令,目的:为了让从节点进行连接超时判断。

从->主:REPLCONF ACK

命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:

REPLCONF ACK <replication_offset>

其中 replication_offset 是从服务器当前的复制偏移量。发送 REPLCONF ACK 命令对于主从服务器有三个作用:replconf ack

  1. 检测主从服务器的网络连接状态。
  2. 辅助实现 min-slaves 选项。
  3. 检测命令丢失, 从节点发送了自身的 slave_replication_offset,主节点会用自己的 master_replication_offset 对比,如果从节点数据缺失,主节点会从 repl_backlog_buffer缓冲区中找到并推送缺失的数据。注意,offset repl_backlog_buffer 缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。

3.1.3总结

小结

学习了Redis的主从库同步的基本原理,总结来说,有三种模式:全量复制、基于长连接的命令传播,以及增量复制。

全量复制虽然耗时,但是对于从库来说,如果是第一次同步,全量复制是无法避免的,所以,我给你一个小建议:一个Redis实例的数据库不要太大,一个实例大小在几GB级别比较合适,这样可以减少RDB文件生成、传输和重新加载的开销。另外,为了避免多个从库同时和主库进行全量复制,给主库过大的同步压力,我们也可以采用“主-从-从”这一级联模式,来缓解主库的压力。

长连接复制是主从库正常运行后的常规同步阶段。在这个阶段中,主从库之间通过命令传播实现同步。不过,这期间如果遇到了网络断连,增量复制就派上用场了。我特别建议你留意一下repl_backlog_size这个配置参数。如果它配置得过小,在增量复制阶段,可能会导致从库的复制进度赶不上主库,进而导致从库重新进行全量复制。所以,通过调大这个参数,可以减少从库在网络断连时全量复制的风险。

不过,主从库模式使用读写分离虽然避免了同时写多个实例带来的数据不一致问题,但是还面临主库故障的潜在风险。

基本总结

每个从库会记录自己的 slave_repl_offset,每个从库的复制进度也不一定相同。(从库会记录收到的偏移量,并且每个从库的复制进度可能是不一致的)

在和主库重连进行恢复时,从库会通过 psync 命令把自己记录的 slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。(根据psync发送过来的runid跟偏移量offerset数据判断是否进行全量复制)

replication buffer 和 repl_backlog_buffer

  1. replication buffer 对应于每个 slave,通过 config set client-output-buffer-limit slave设置。
  2. repl_backlog_buffer是一个环形缓冲区,整个 master 进程中只会存在一个,所有的 slave 公用。repl_backlog 的大小通过 repl-backlog-size 参数设置,默认大小是 1M,其大小可以根据每秒产生的命令、(master 执行 rdb bgsave) +( master 发送 rdb 到 slave) + (slave load rdb 文件)时间之和来估算积压缓冲区的大小,repl-backlog-size 值不小于这两者的乘积。

总的来说,replication buffer 是主从库在进行全量复制时,主库上用于和从库连接的客户端的 buffer,而 repl_backlog_buffer 是为了支持从库增量复制,主库上用于持续保存写操作的一块专用 buffer。

Redis主从库在进行复制时,当主库要把全量复制期间的写操作命令发给从库时,主库会先创建一个客户端(replication buffer),用来连接从库,然后通过这个客户端,把写操作命令发给从库。在内存中,主库上的客户端就会对应个buffer,这个buffer就被称为replication buffer。Redis通过client_buffer配置项来控制这个buffer的大小。主库会给每个从库建立一个客户端,所以replication buffer不是共享的,而是每个从库都有一个对应的客户端。

repl_backlog_buffer是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的。主库和从库会各自记录自己的复制进度,所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset)发给主库,主库就可以和它独立同步。

5. 主从应用问题

主从复制下,从节点会删除过期的数据吗?

为了主从节点的数据一致性,从节点不会主动删除数据

5.1 Redis 有两种删除策略:

  1. 惰性删除:当客户端查询对应的数据时,Redis 判断该数据是否过期,过期则删除。
  2. 定期删除:Redis 通过定时任务删除过期数据。

那客户端通过从节点读取数据会不会读取到过期数据?

Redis 3.2 开始,通过从节点读取数据时,先判断数据是否已过期。如果过期则不返回客户端,并且删除数据。

5.2 单机内存大小限制

如果 Redis 单机内存达到 10GB,一个从节点的同步时间在几分钟的级别;如果从节点较多,恢复的速度会更慢。如果系统的读负载很高,而这段时间从节点无法提供服务,会对系统造成很大的压力。

如果数据量过大,全量复制阶段主节点 fork + 保存 RDB 文件耗时过大,从节点长时间接收不到数据触发超时,主从节点的数据同步同样可能陷入全量复制->超时导致复制中断->重连->全量复制->超时导致复制中断……的循环。

此外,主节点单机内存除了绝对量不能太大,其占用主机内存的比例也不应过大:最好只使用 50% - 65% 的内存,留下 30%-45% 的内存用于执行 bgsave 命令和创建复制缓冲区等。

6.总结

  1. 主从复制的作用:AOF 和 RDB 二进制文件保证了宕机快速恢复数据,尽可能的防止丢失数据。但是宕机后依然无法提供服务,所以便演化出主从架构、读写分离。
  2. 主从复制原理:连接建立阶段、数据同步阶段、命令传播阶段;数据同步阶段又分为 全量复制和部分复制;命令传播阶段主从节点之间有 PING 和 REPLCONF ACK 命令互相进行心跳检测。
  3. 主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注。

二、Redis主从同步与故障切换会遇到的问题

Redis 的主从同步机制不仅可以让从库服务更多的读请求,分担主库的压力,而且还能在主库发生故障时,进行主从库切换,提供高可靠服务。

在实际使用主从机制的时候,会遇到三个问题:分别是主从数据不一致、读到过期数据,以及配置项设置得不合理从而导致服务挂掉。

1.主从数据不一致

主从数据不一致,就是指客户端从从库中读取到的值和主库中的最新值并不一致。

1.1例子

假设主从库之前保存的用户年龄值是 19,但是主库接收到了修改命令,已经把这个数据更新为 20 了,但是,从库中的值仍然是 19。那么,如果客户端从从库中读取用户年龄值,就会读到旧值。

1.2主要原因:

主从库间的命令复制是异步进行的

具体来说,在主从库命令传播阶段,主库收到新的写命令后,会发送给从库。但是,主库并不会等到从库实际执行完命令后,再把结果返回给客户端,而是主库自己在本地执行完命令后,就会向客户端返回结果了。如果从库还没有执行主库同步过来的命令,主从库间的数据就不一致了。

1.3从库会滞后执行同步命令原因

原因一:网络传输延迟

主从库间的网络可能会有传输延迟,所以从库不能及时地收到主库发送的命令,从库上执行同步命令的时间就会被延后

原因二:受到阻塞

即使从库及时收到了主库的命令,也可能会因为正在处理其它复杂度高的命令(例如集合操作命令)而阻塞。从库需要处理完当前的命令,才能执行主库发送的命令操作,这就会造成主从数据不一致。而在主库命令被滞后处理的这段时间内,主库本身可能又执行了新的写操作。这样一来,主从库间的数据不一致程度就会进一步加剧。

1.4解决方式

方式一:使配置网络良好

在硬件环境配置方面,我们要尽量保证主从库间的网络连接状况良好。例如,我们要避免把主从库部署在不同的机房,或者是避免把网络通信密集的应用(例如数据分析应用)和 Redis 主从库部署在一起。

方式二:外部程序帮助干预

开发一个外部程序来监控主从库间的复制进度

因为 Redis 的 INFO replication 命令可以查看主库接收写命令的进度信息(master_repl_offset)和从库复制写命令的进度信息(slave_repl_offset),可以开发一个监控程序,先用 INFO replication 命令查到主、从库的进度,用 master_repl_offset 减去 slave_repl_offset,得到从库和主库间的复制进度差值。

如果某个从库的进度差值大于预设的阈值,让客户端不再和这个从库连接进行数据读取,减少读到不一致数据的情况。为了避免出现客户端和所有从库都不能连接的情况,需要把复制进度差值的阈值设置得大一些。

可以周期性地运行这个流程来监测主从库间的不一致情况

监控程序可以一直监控着从库的复制进度,当从库的复制进度又赶上主库时,允许客户端再次跟这些从库连接。

2.读取过期数据

在使用 Redis 主从集群时,有时会读到过期数据。例如,数据 X 的过期时间是 202010240900,但是客户端在 202010240910 时,仍然可以从从库中读到数据 X

2.1客户端可以读取过期数据原因

这是由 Redis 的过期数据删除策略引起的,Redis 同时使用了两种策略来删除过期的数据,分别是惰性删除策略和定期删除策略。

惰性删除策略:过期不立即删除,有请求再次读取时删除

当一个过期键被访问时,Redis会检查这个键是否过期。如果过期,就会立即删除。如果没有过期,Redis会像处理普通键一样继续处理它。这种方式被称为惰性删除,因为Redis只会在需要访问一个键时才会检查它是否过期,并在发现它过期后删除它。

这个策略的好处是尽量减少删除操作对 CPU 资源的使用,对于用不到的数据,就不再浪费时间进行检查和删除。但是,这个策略会导致大量已经过期的数据留存在内存中,占用较多的内存资源,Redis 在使用这个策略的同时,还使用了第二种策略:定期删除策略。

定期删除策略:一段时间对所有过期的键值进行删除

Redis也使用一种定期删除策略来删除过期键。在这种策略下,Redis会每隔一段时间扫描数据库,查找所有已过期的键并将它们删除。这种方式称为定期删除,因为Redis会定期执行这个操作

说明

惰性删除策略可以让Redis更高效地使用内存,因为它只有在需要访问一个键时才会检查它是否过期。但是,它可能会导致过期键长时间滞留在内存中,直到下次被访问。定期删除策略则可以保证过期键及时被删除,但可能会对性能产生一定的影响,因为它需要周期性地遍历整个数据库。为了平衡内存使用和性能,Redis通常会同时使用这两种策略。

定期删除策略可以释放一些内存,Redis 为了避免过多删除操作对性能产生影响,每次随机检查数据的数量并不多。如果过期数据很多,并且一直没有再被访问的话,这些数据就会留存在 Redis 实例中。业务应用之所以会读到过期数据,这些留存数据就是一个重要因素。

惰性删除策略实现后,数据只有被再次访问时,才会被实际删除。如果客户端从主库上读取留存的过期数据,主库会触发删除操作,此时,客户端并不会读到过期数据。但是,从库本身不会执行删除操作,如果客户端在从库中访问留存的过期数据,从库并不会触发数据删除

从库会给客户端返回过期数据吗?

Redis3.2版本之前,从库会返回过期数据。Redis3.2之后从库不会返回过期数据,会返回空值,但是还是有可能会读到。

如果使用的是 Redis 3.2 之前的版本,那么,从库在服务读请求时,并不会判断数据是否过期,而是会返回过期数据。
在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免了客户端读到过期数据。所以,在应用主从集群时,尽量使用 Redis 3.2 及以上版本。

使用了 Redis 3.2 后的版本,还是会读到过期数据,这跟 Redis 用于设置过期时间的命令有关系,有些命令给数据设置的过期时间在从库上可能会被延后,导致应该过期的数据又在从库上被读取到

设置数据过期时间命令说明

  1. EXPIRE 和 PEXPIRE:它们给数据设置的是从命令执行时开始计算的存活时间;
  2. EXPIREAT 和 PEXPIREAT:它们会直接把数据的过期时间设置为具体的一个时间点。

例子

第一个例子是使用 EXPIRE 命令,当执行下面的命令时,testkey 的过期时间设置为 60s 后。

expire testkey 60

第二个例子是使用 EXPIREAT 命令,例如,让 testkey 在 2020 年 10 月 24 日上午 9 点过期,命令中的 1603501200 就是以秒数时间戳表示的 10 月 24 日上午 9 点。

expireat testkey 1603501200

命令如何导致读到过期数据

主从库全量同步,收到EXPIRE命令,主库直接执行,全量同步完成发送给从库,从库执行的时候会在当前时间基础上加上存活时间,过期时间明显延后

例子说明

假设当前时间是 2020 年 10 月 24 日上午 9 点,主从库正在同步,主库收到了一条命令:EXPIRE testkey 60,这就表示,testkey 的过期时间就是 24 日上午 9 点 1 分,主库直接执行了这条命令。

但是,主从库全量同步花费了 2 分钟才完成。等从库开始执行这条命令时,时间已经是 9 点 2 分了。而 EXPIRE 命令是把 testkey 的过期时间设置为当前时间的 60s 后,也就是 9 点 3 分。如果客户端在 9 点 2 分 30 秒时在从库上读取 testkey,仍然可以读到 testkey 的值。但是,testkey 实际上已经过期

为了避免这种情况,在业务应用中使用 EXPIREAT/PEXPIREAT 命令,把数据的过期时间设置为具体的时间点,避免读到过期数据

3.部分总结

  1. 主从数据不一致。Redis 采用的是异步复制,所以无法实现强一致性保证(主从数据时时刻刻保持一致),数据不一致是难以避免的。需要做的:保证良好网络环境,使用程序监控从库复制进度,一旦从库复制进度超过阈值,不让客户端连接从库

  1. 读到过期数据,这是可以提前规避的,一个方法是,使用 Redis 3.2 及以上版本;可以使用 EXPIREAT/PEXPIREAT 命令设置过期时间,避免从库上的数据过期时间滞后。需要注意,因为 EXPIREAT/PEXPIREAT 设置的是时间点,所以,主从节点上的时钟要保持一致,具体的做法是,让主从节点和相同的 NTP 服务器(时间服务器)进行时钟同步

4.不合理配置项导致的服务挂掉

涉及两个配置项:分别是 protected-mode 和 cluster-node-timeout。

1.protected-mode配置项

这个配置项的作用是限定哨兵实例能否被其他服务器访问。当配置设置为yes,哨兵实例只能被本地服务器访问(部署这个实例的算本地)。设置为no时,其他服务器也能访问哨兵实例。

如果protected-mode被设置为yes,而其余哨兵实例部署在其它服务器,这些哨兵实例间就无法通信。主库故障,哨兵无法判断主库下线,无法进行主从切换,最终Redis服务不可用。

在应用主从集群时,要注意将protected-mode配置项设置为no,并且将bind配置项设置为其它哨兵实例的IP地址。只有在bind中设置了IP地址的哨兵,才可以访问当前实例,既保证了实例间能够通信进行主从切换,也保证了哨兵的安全性。

例子:

如果设置了下面的配置项,那么,部署在192.168.10.3/4/5这三台服务器上的哨兵实例就可以相互通信,执行主从切换。

protected-mode no
bind 192.168.10.3 192.168.10.4 192.168.10.5

2.cluster-node-timeout配置项

这个配置项设置了Redis Cluster 中实例响应心跳消息的超时时间。

在Redis Cluster集群中为每个实例配置了“一主一从”模式时,如果主实例发生故障,从实例会切换为主实例,受网络延迟和切换操作执行的影响,切换时间可能较长,就会导致实例的心跳超时(超出cluster-node-timeout)。实例超时后,就会被Redis Cluster判断为异常。而 Redis Cluster 正常运行的条件就是,有半数以上的实例都能正常运行。

如果执行主从切换的实例超过半数,而主从切换时间又过长的话,就可能有半数以上的实例心跳超时,从而可能导致整个集群挂掉。建议将cluster-node-timeout调大些(例如10到20秒)。

5.总结

主从库同步时可能出现的 3 个问题:分别是主从数据不一致、读取到过期数据和不合理配置项导致服务挂掉

关于主从库数据不一致的问题

Redis 中的 slave-serve-stale-data 配置项设置了从库能否处理数据读写命令,把它设置为 no,从库只能服务 INFO、SLAVEOF 命令,避免在从库中读到不一致的数据。

lave-serve-stale-data配置项和 slave-read-only 的区别

slave-read-only 是设置从库能否处理写命令,slave-read-only 设置为 yes 时,从库只能处理读请求,无法处理写请求

把 slave-read-only 设置为 no,让从库也能直接删除数据,以此来避免读到过期数据,是个好方法吗?

答案:主从复制中的增删改操作都需要在主库执行,即使从库能做删除,也不要在从库删除,否则会导致数据不一致。主从库的时钟不同步,导致主从库删除时间不一致。expire被延迟执行

对于已经设置了过期时间的 key,主库在 key 快要过期时,使用 expire 命令重置了过期时间,例如,一个 key 原本设置为 10s 后过期,在还剩 1s 就要过期时,主库又用 expire 命令将 key 的过期时间设置为 60s 后。但是,expire 命令从主库传输到从库时,由于网络延迟导致从库没有及时收到 expire 命令(比如延后了 3s 从库才收到 expire 命令),所以,从库按照原定的过期时间删除了过期 key,这就导致主从数据不一致了。(主从同步之后,从库才会执行expire命令)

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Recently 祝祝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值