如果master复制缓冲区找不到offset
之后的数据,说明断开的时间太久,复制缓冲区的内容已经被新的内容覆盖了,此时master只能触发全量数据同步。
slave经过全量同步或部分同步后,之后master实时产生的写入,是如何实时同步的?
很简单,master每次执行完新的写入命令后,也会把这个命令实时地传播给slave,slave执行与master相同的操作,就可以实时与master保持一致。
需要注意的是,master传播给slave的命令是异步执行的,也就是说在master上写入后,马上在slave上查询是有可能查不到的,因为异步执行存在一定的延迟。
slave与master建立连接后,slave就属于master的一个client,master会为每个client分配一个client output buffer
,master和每个client通信都会先把数据写入到这个内存buffer
中,再通过网络发送给这个client。
但是,由于这个buffer
是占用Redis实例内存的,所以不能无限大。所以Redis提供了控制buffer
大小的参数限制:
普通client buffer限制
client-output-buffer-limit normal 0 0 0
slave client buffer限制
client-output-buffer-limit slave 256mb 64mb 60
pubsub client buffer限制
client-output-buffer-limit pubsub 32mb 8mb 60
这个参数的格式为:client-output-buffer-limit $type $hard_limit $soft_limit $soft_seconds
,其含义为:如果client的buffer
大小达到了hard_limit
或在达到了soft_limit
并持续了soft_seconds
时间,那么Redis会强制断开与client的连接。
对于slave的client,默认的限制是,如果buffer
达到了256MB,或者达到64MB并持续了1分钟,那么master就会强制断开slave的连接。
这个配置的大小在某些场景下,也会影响到主从的数据同步,我们下面会具体介绍到。
在命令传播阶段,为了保证master-slave数据同步的稳定进行,Redis还设计了一些机制维护这个复制链路,这种机制主要通过心跳来完成,主要包括两方面:
-
master定时向slave发送
ping
,检查slave是否正常 -
slave定时向master发送
replconf ack $offset
,告知master自己复制的位置
在master这一侧,master向slave发送ping
的频率由repl-ping-slave-period
参数控制,默认10秒,它的主要作用是让slave节点进行超时判断,如果slave在规定时间内没有收到master的心跳,slave会自动释放与master的连接,这个时间由repl-timeout
决定,默认60秒。
同样,在slave这边,它也会定时向master发送replconf ack $offset
命令,频率为每1秒一次,其中offset
是slave当前复制到的数据偏移量,这么做的主要作用如下:
-
让master检测slave的状态:如果master超过
repl-timeout
时间未收到slave的replconf ack $offset
命令,则master主动断开与slave的连接 -
master检测slave丢失的命令:master根据slave发送的
offset
并与自己对比,如果发现slave发生了数据丢失,master会重新发送丢失的数据,前提是master的复制缓冲区中还保留这些数据,否则会触发全量同步 -
数据安全性保障:Redis提供了
min-slaves-to-write
和min-slaves-max-lag
参数,用于保障master在不安全的情况下禁止写入,min-slaves-to-write
表示至少存在N个slave节点,min-slaves-max-lag
表示slave延迟必须小于这个时间,那么master才会接收写命令,否则master认为slave节点太少或延迟过大,这种情况下是数据不安全的,实现这个机制就依赖slave定时发送replconf ack $offset
让master知晓slave的情况,一般情况下,我们不会开启这个配置,了解一下就好
可见,master和slave节点通过心跳机制共同维护它们之间数据同步的稳定性,并在同步过程中发生问题时可以及时自动恢复。
我们可以可以在master上执行info
命令查看当前所有slave的同步情况:
role:master # redis的角色
connected_slaves:1 # slave节点数
slave0:ip=127.0.0.1,port=6480,state=online,offset=22475,lag=0 # slave信息、slave复制到的偏移位置、距离上一次slave发送心跳的时间间隔(秒)
master_repl_offset:22475 # master当前的偏移量
repl_backlog_ 需要zi料+ 绿色徽【vip1024b】
active:1 # master有可用的复制缓冲区
repl_backlog_size:1048576 # master复制缓冲区大小
通过这些信息,我们能看到slave与master的数据同步情况,例如延迟了多大的数据,slave多久没有发送心跳给master,以及master的复制缓冲区大小。
===========================================================================
在整个数据复制的过程中,故障是时有发生的,例如网络延迟过大、网络故障、机器故障等。
所以在复制过程中,有一些情况需要我们格外注意,必要时需要针对性进行参数配置的调整,否则同步过程中会发生很多意外问题。
主要问题分为以下几个方面,下面依次来介绍。
上面我们有提到,主从建立同步时,优先检测是否可以尝试只同步部分数据,这种情况就是针对于之前已经建立好了复制链路,只是因为故障导致临时断开,故障恢复后重新建立同步时,为了避免全量同步的资源消耗,Redis会优先尝试部分数据同步,如果条件不符合,才会触发全量同步。
这个判断依据就是在master上维护的复制缓冲区大小,如果这个缓冲区配置的过小,很有可能在主从断开复制的这段时间内,master产生的写入导致复制缓冲区的数据被覆盖,重新建立同步时的slave需要同步的offset
位置在master的缓冲区中找不到,那么此时就会触发全量同步。
如何避免这种情况?解决方案就是适当调大复制缓冲区repl-backlog-size
的大小,这个缓冲区的大小默认为1MB,如果实例写入量比较大,可以针对性调大此配置。
但这个配置不能调的无限大,因为它会额外占用内存空间。如果主从断开复制的时间过长,那么触发全量复制在所难免的,我们需要保证主从节点的网络质量,避免频繁断开复制的情况发生。
主从经过全量同步和部分同步后,之后master产生了写入命令,会实时传播给slave节点,如果在这个过程中发生了复制断开,那么一定是在这个过程中产生了问题。我们来分析这个过程是如何处理命令传播的。
上面我们也提到了,主从建立同步链路后,由于slave也是master的一个client,master会对每个client维护一个client output buffer
,master产生写命令执行完成后,会把这个命令写入到这个buffer
中,然后等待Redis的网络循环事件把buffer
中数据通过Socket发送给slave,发送成功后,master释放buffer
中的内存。
如果master在写入量非常大的情况下,可能存在以下情况会导致master的client output buffer
内存持续增长:
-
主从节点机器存在一定网络延迟(例如机器网卡负载比较高),master无法及时的把数据发送给slave
-
slave由于一些原因无法及时处理master发来的命令(例如开启了AOF并实时刷盘,磁盘IO负载高)
当遇到上面情况时,master的client output buffer
持续增长,直到触发默认配置的阈值限制client-output-buffer-limit slave 256mb 64mb 60
,那么master则会把这个slave连接强制断开,这就会导致复制中断。
之后slave重新发送复制请求,但是以上原因可能依旧存在,经过一段时间后又产生上述问题,主从连接再次被断开,周而复始,主从频繁断开链接,无法正常复制数据。
解决方案是,适当调大client-output-buffer-limit
的阈值,并且解决slave写入慢的情况,保证master发给slave的数据可以很快得处理完成,这样才能避免频繁断开复制的问题。
当主从建立同步进行全量同步数据时,master会fork
出一个子进程,扫描全量数据写入到RDB文件中。
这个fork
操作,并不是没有代价的。fork
在创建子进程时,需要父进程拷贝一份内存页表给子进程,如果master占用的内存过大,那么fork
时需要拷贝的内存页表也会比较耗时,在完成fork
之前,Redis整个进程都会阻塞住,无法处理任何的请求,所以业务会发现Redis突然变慢了,甚至发生超时的情况。
我们可以执行info
可以看到latest_fork_usec
参数,单位微妙。这就是最后一次fork
的耗时时间,我们可以根据这个时间来评估fork
时间是否符合预期。
对于这种情况,可以优化方案如下:
-
一定保证机器拥有足够的CPU资源和内存资源
-
单个Redis实例内存不要太大,大实例拆分成小实例
通过以上方式避免fork
引发的父进程长时间阻塞问题。
之前我们已经了解到,主从全量复制会经过3个阶段:
最后
金三银四到了,送上一个小福利!
之前我们已经了解到,主从全量复制会经过3个阶段:
最后
金三银四到了,送上一个小福利!
[外链图片转存中…(img-ejRmf5rx-1710365587901)]
[外链图片转存中…(img-W4zCx2ho-1710365587902)]
[外链图片转存中…(img-c3avBsVL-1710365587902)]