Redis之深入理解主从复制原理

Redis之深入理解主从复制原理

  • 一、为什么redis需要分布式方案
  • 二.、Redis主从工作原理
    • 1、全量同步
    • 2、增量同步
    • 3、命令传播
    • 4、心跳机制
  • 三、主从集群优化
    • 1、优化方案
    • 2、相关知识
    • 3、复制过程中的问题
  • 四、总结

个人复习笔记

一、为什么redis需要分布式方案

1、实现更高性能:高并发应用,单机性能会有影响,需要更多redis服务器分担压力,实现负载均衡

2、实现高可用:如果单机,防止宕机/硬件故障

3、实现可扩展:单机内存和硬件有限制,实现横向扩展

冗余或者分片存储实现如上特性。

二.、Redis主从工作原理

假设有A、B两个实例,如何让B作为A的slave节点?

  • 在B节点执行命令:slaveof A的IP A的端口

1、全量同步

主从第一次连接时会执行全量同步,将master节点所有的数据拷贝给slave节点。
Redis之深入理解主从复制原理_第1张图片

全量同步过程

1、slave节点请求增量同步
2、master检查replid不一致,拒绝增量同步,开始全量同步
3、master将完整的内存数据生成RDB文件,发送给slave
4、slave清空本地数据,加载RDB文件
5、master将RDB期间接收到的命令记录在repl_baklog,并持续将log中的命令
   发送给slave
6、slave执行接收到的命令,与master保持同步

master如何判断一个slave是否是第一次同步?

slave原本也是一个master,有自己的replication id和offset,与master连接时,将自己的relication id 和offset发送给master,master收到relication id后,发现与自己的relication id不一致,master就可以判断该slave是第一次连接,于是开始全量同步,master会将自己的replid和offset都发送给这个slave,slave保存这些信息,master和slave的replid就保持一致了

2、增量同步

主从建立同步时,优先检测是否可以尝试只同步部分数据,这种情况就是针对于之前已经建立好了复制链路,只是因为故障导致临时断开,故障恢复后重新建立同步时,为了避免全量同步的资源消耗,Redis会优先尝试部分数据同步,如果条件不符合,才会触发全量同步。
Redis之深入理解主从复制原理_第2张图片
增量同步过程

1、slave请求增量同步
2、master检查replid是否一致?
     2.1、 不一致:全量同步
     2.2、一致:去repl_baklog中获取offset后的数据
3、发送offset后的命令
4、执行命令
  • master与slave的数据差异部分在repl_baklog文件,repl_baklog中会记录Redis处理过的命令日志及offset,包括master当前的offset,和slave已经拷贝到的offset;slave和master的offset之间的差异就是slave需要拷贝的数据了;

repl_baklog原理

  • repl_baklog这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,这样数组头部的数据就会被覆盖。
  • 数组一旦写满,再有新数据写入时,就会覆盖数组的旧数据。此时slave来增量同步,发现自己offset已经被覆盖了,此时只能全量同步。

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

因此,我们要想办法避免这一情况,一般而言,我们可以调整 repl_backlog_size 这个参数。这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。

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

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

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

# redis.conf
# 环形缓冲区的大小 为1mb
repl-backlog-size 1mb
# 如果所有从库断开连接 3600 秒后没有从库连接,则释放环形缓冲区
repl-backlog-ttl 3600

3、命令传播

slave经过全量同步或部分同步后,之后master实时产生的写入,是如何实时同步的?
一旦主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,可以避免频繁建立连接的开销。

需要注意的是,master传播给slave的命令是异步执行的,也就是说在master上写入后,马上在slave上查询是有可能查不到的,因为异步执行存在一定的延迟。
slave与master建立连接后,slave就属于master的一个client,master会为每个client分配一个client output buffer,master和每个client通信都会先把数据写入到这个内存buffer中,再通过网络发送给这个client(原理请看:redis输入输出缓冲区)。

但是,由于这个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的连接。

这个配置的大小在某些场景下,也会影响到主从的数据同步,我们下面会具体介绍到。

4、心跳机制

在命令传播阶段,为了保证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_active:1       # master有可用的复制缓冲区
repl_backlog_size:1048576   # master复制缓冲区大小

三、主从集群优化

1、优化方案

  • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
  • 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力
  • Redis之深入理解主从复制原理_第5张图片

2、相关知识

全量同步和增量同步区别?

全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步?

slave节点第一次连接master节点时。
slave节点断开时间太久,repl_baklog中的offset已经被覆盖时。

什么时候执行增量同步?

网络抖动,slave节点断开又恢复,并且在repl_baklog中能找到offset时。

无硬盘复制
前面介绍Redis复制的工作原理时介绍了复制是基于RDB方式的持久化实现的,即主数 据库端在后台保存 RDB 快照,从数据库端则接收并载入快照文件。这样的实现优点是可以 显著地简化逻辑,复用已有的代码,但是缺点也很明显。

当主数据库禁用RDB快照时(即删除了所有的配置文件中的save语句),如果执行 了复制初始化操作,Redis依然会生成RDB快照,所以下次启动后主数据库会以该快照恢复数 据。因为复制发生的时间不能确定,这使得恢复的数据可能是任何时间点的。
因为复制初始化时需要在硬盘中创建RDB快照文件,所以如果硬盘性能很慢(如 网络硬盘)时这一过程会对性能产生影响。举例来说,当使用 Redis 做缓存系统时,因为不 需要持久化,所以服务器的硬盘读写速度可能较差。但是当该缓存系统使用一主多从的集群 架构时,每次和从数据库同步,Redis都会执行一次快照,同时对硬盘进行读写,导致性能降 低。
因此从2.8.18版本开始,Redis引入了无硬盘复制选项,开启该选项时,Redis在与从数据 库进行复制初始化时将不会将快照内容存储到硬盘上,而是直接通过网络发送给从数据库, 避免了硬盘的性能瓶颈。

目前无硬盘复制的功能还在试验阶段,可以在配置文件中使用如下配置来开启该功能: repl-diskless-sync yes。

3、复制过程中的问题

在整个数据复制的过程中,故障是时有发生的,例如网络延迟过大、网络故障、机器故障等。

所以在复制过程中,有一些情况需要我们格外注意,必要时需要针对性进行参数配置的调整,否则同步过程中会发生很多意外问题。

主要问题分为以下几个方面,下面依次来介绍。

主从断开复制后,重新复制触发了全量同步?
上面我们有提到,主从建立同步时,优先检测是否可以尝试只同步部分数据,这种情况就是针对于之前已经建立好了复制链路,只是因为故障导致临时断开,故障恢复后重新建立同步时,为了避免全量同步的资源消耗,Redis会优先尝试部分数据同步,如果条件不符合,才会触发全量同步。

这个判断依据就是在master上维护的复制缓冲区大小,如果这个缓冲区配置的过小,很有可能在主从断开复制的这段时间内,master产生的写入导致复制缓冲区的数据被覆盖,重新建立同步时的slave需要同步的offset位置在master的缓冲区中找不到,那么此时就会触发全量同步。

如何避免这种情况?解决方案就是适当调大复制缓冲区repl-backlog-size的大小,这个缓冲区的大小默认为1MB,如果实例写入量比较大,可以针对性调大此配置。

但这个配置不能调的无限大,因为它会额外占用内存空间。如果主从断开复制的时间过长,那么触发全量复制在所难免的,我们需要保证主从节点的网络质量,避免频繁断开复制的情况发生。

master写入量很大,主从断开复制?
主从经过全量同步和部分同步后,之后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的数据可以很快得处理完成,这样才能避免频繁断开复制的问题。

添加slave节点,master发生阻塞?

当主从建立同步进行全量同步数据时,master会fork出一个子进程,扫描全量数据写入到RDB文件中。

这个fork操作,并不是没有代价的。fork在创建子进程时,需要父进程拷贝一份内存页表给子进程,如果master占用的内存过大,那么fork时需要拷贝的内存页表也会比较耗时,在完成fork之前,Redis整个进程都会阻塞住,无法处理任何的请求,所以业务会发现Redis突然变慢了,甚至发生超时的情况。

我们可以执行info可以看到latest_fork_usec参数,单位微妙。这就是最后一次fork的耗时时间,我们可以根据这个时间来评估fork时间是否符合预期。

对于这种情况,可以优化方案如下:

  • 一定保证机器拥有足够的CPU资源和内存资源
  • 单个Redis实例内存不要太大,大实例拆分成小实例

通过以上方式避免fork引发的父进程长时间阻塞问题。

主从全量同步数据时很慢?

之前我们已经了解到,主从全量复制会经过3个阶段:

  • master生成RDB文件
  • master把RDB文件发送给slave
  • slave清空数据库,加载RDB文件到内存

如果发现全量同步数据非常耗时,我们根据以上阶段来分析原因:

  • master实例数据比较大,并且机器的CPU负载较高时,在生成RDB时耗大量CPU资源,导致RDB生成很慢
  • master和slave的机器网络带宽被打满,导致master发送给slave的RDB文件网络传输时变慢
  • slave机器内存不够用,但开启了swap机制,导致内存不足以加载RDB文件,数据被写入到磁盘上,导致数据加载变慢

通过以上情况可以看出,主从复制时,会消耗CPU、内存、网卡带宽各方面的资源,我们需要合理规划服务器资源,保证资源的充足。并且针对大实例进行拆分,能避免很多复制中的问题。

四、总结

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

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

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

你可能感兴趣的:(redis,redis)