个人复习笔记
1、实现更高性能:高并发应用,单机性能会有影响,需要更多redis服务器分担压力,实现负载均衡
2、实现高可用:如果单机,防止宕机/硬件故障
3、实现可扩展:单机内存和硬件有限制,实现横向扩展
冗余或者分片存储实现如上特性。
假设有A、B两个实例,如何让B作为A的slave节点?
主从第一次连接时会执行全量同步,将master节点所有的数据拷贝给slave节点。
全量同步过程
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就保持一致了
主从建立同步时,优先检测是否可以尝试只同步部分数据,这种情况就是针对于之前已经建立好了复制链路,只是因为故障导致临时断开,故障恢复后重新建立同步时,为了避免全量同步的资源消耗,Redis会优先尝试部分数据同步,如果条件不符合,才会触发全量同步。
增量同步过程
1、slave请求增量同步
2、master检查replid是否一致?
2.1、 不一致:全量同步
2.2、一致:去repl_baklog中获取offset后的数据
3、发送offset后的命令
4、执行命令
repl_baklog原理
如下图示:
不过,有一个地方我要强调一下,因为 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
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的连接。
这个配置的大小在某些场景下,也会影响到主从的数据同步,我们下面会具体介绍到。
在命令传播阶段,为了保证master-slave数据同步的稳定进行,Redis还设计了一些机制维护这个复制链路,这种机制主要通过心跳来完成,主要包括两方面:
同样,在slave这边,它也会定时向master发送replconf ack $offset命令,频率为每1秒一次,其中offset是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复制缓冲区大小
全量同步和增量同步区别?
全量同步: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。
在整个数据复制的过程中,故障是时有发生的,例如网络延迟过大、网络故障、机器故障等。
所以在复制过程中,有一些情况需要我们格外注意,必要时需要针对性进行参数配置的调整,否则同步过程中会发生很多意外问题。
主要问题分为以下几个方面,下面依次来介绍。
主从断开复制后,重新复制触发了全量同步?
上面我们有提到,主从建立同步时,优先检测是否可以尝试只同步部分数据,这种情况就是针对于之前已经建立好了复制链路,只是因为故障导致临时断开,故障恢复后重新建立同步时,为了避免全量同步的资源消耗,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的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时间是否符合预期。
对于这种情况,可以优化方案如下:
通过以上方式避免fork引发的父进程长时间阻塞问题。
主从全量同步数据时很慢?
之前我们已经了解到,主从全量复制会经过3个阶段:
如果发现全量同步数据非常耗时,我们根据以上阶段来分析原因:
通过以上情况可以看出,主从复制时,会消耗CPU、内存、网卡带宽各方面的资源,我们需要合理规划服务器资源,保证资源的充足。并且针对大实例进行拆分,能避免很多复制中的问题。
本篇文章主要讲解了 Redis 的主从库同步的基本原理,总结来说,有三种模式:全量复制、基于长连接的命令传播,以及增量复制。
全量复制虽然耗时,但是对于从库来说,如果是第一次同步,全量复制是无法避免的,所以,我给你一个小建议:一个 Redis 实例的数据库不要太大,一个实例大小在几 GB 级别比较合适,这样可以减少 RDB 文件生成、传输和重新加载的开销。另外,为了避免多个从库同时和主库进行全量复制,给主库过大的同步压力,我们也可以采用“主 - 从 - 从”这一级联模式,来缓解主库的压力。
长连接复制是主从库正常运行后的常规同步阶段。在这个阶段中,主从库之间通过命令传播实现同步。不过,这期间如果遇到了网络断连,增量复制就派上用场了。我特别建议你留意一下 repl_backlog_size 这个配置参数。如果它配置得过小,在增量复制阶段,可能会导致从库的复制进度赶不上主库,进而导致从库重新进行全量复制。所以,通过调大这个参数,可以减少从库在网络断连时全量复制的风险。