美文网首页
Redis复制的原理和优化

Redis复制的原理和优化

作者: 薛之谦chj | 来源:发表于2020-04-24 18:39 被阅读0次

    什么是主从复制

    单机有什么问题?

    机器故障,容量瓶颈,QBS瓶颈,

    主从复制的作用

    提供了一主多从,数据副本,扩展读性能

    简单总结:

    1.一个master(主)可以有多个slave

    2.一个slace只能有一个master

    3.数据流向是单向的,master到slave

    主从复制的配置

    两种实现方式: slaveof命令 ,配置

    slaveof命令: slaveof no one(取消复制,就取消掉主从的连接) 从表配置连接主表slaveof ip port (主从连接)

    修改配置:

    slaveof ip port  (连接主节点)

    slave-read-only yes (只读)

    两种方式的比较:

    命令:

    优点:无需重启

    缺点:不便于管理

    统一配置:

    优点:统一配置管理

    缺点:需要重启

    全量复制和部分复制

    全量复制:

    redis全量复制的原理是,首先将master本身的RDB文件同步给slave,而在同步期间,master写入的命令也会记录下来(master内部有一个复制缓冲区,会记录同步时master新增的写入),当slave将RDB加载完后,会通过偏移量的对比将这期间master写入的值同步给slave。

    来看一张完整的复制流程图:

    1. slave内部首先会发送一个psync的命令给master 这个命令第一个参数是runId,第二个参数是偏移量,而由于是第一次复制,slave不知道master的runId,也不知道自己偏移量,这时候会传一个问号和-1,告诉master节点是第一次同步。

    2. 当master接受到psync ? -1 时,就知道slave是要全量复制,就会将自己的runID和offset告知slave

    3.slave会将master信息保存

    4.master这时会做一个RDB的生成(bgsave)

    5.将RDB发送给slave

    6.将复制缓冲区记录的操作也发送给slave

    7.slave清空自己的所有老数据

    8.slave这时就会加载RDB文件以及复制缓冲区数据,完成同步。

    全量复制的开销:

    1.bgsave的开销,每次bgsave需要fork子进程,对内存和CPU的开销很大

    2.RDB文件网络传输的时间(网络带宽)

    3.从节点清空数据的时间

    4.从节点加载RDB的时间

    5.可能的AOF重写时间(如果我们的从节点开启了AOF,则加载完RDB后会对AOF进行一个重写,保证AOF是最新的)

    部分复制:

    为什么要部分复制? 在redis2.8版本之前,如果master和slave之间的网络发生了抖动连接断开,就会导致slave完全不知道master的动作,同步就会出问题,而为了保证数据一致,等网络恢复后进行一次全量复制。而全量复制的开销是很大的,redis2.8版本就提个了一个部分复制的功能。

    部分复制的实现原理:

    当master和slave断开连接时,master会将期间所做的操作记录到复制缓存区当中(可以看成是一个队列,其大小默认1M)。待slave重连后,slave会向master发送psync命令并传入offset和runId,这时候,如果master发现slave传输的偏移量的值,在缓存区队列范围中,就会将从offset开始到队列结束的数据传给slave,从而达到同步,降低了使用全量复制的开销。

    注意:

    正常情况下redis是如何决定是全量复制还是部分复制?

    从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:

    1.如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;

    2.如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。

    服务器运行ID(runid)

    每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。 通过info server命令,可以查看节点的runid。

    主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:

    如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况)

    如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

    缓冲区大小调节:

    由于缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置repl-backlog-size)来设置;例如如果网络中断的平均时间是60s,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为100KB,则复制积压缓冲区的平均需求为6MB,保险起见,可以设置为12MB,来保证绝大多数断线情况都可以使用部分复制。

    故障处理

    A是redis主服务器,B是从服务器。A机器故障暂无法修复。现将C机器上的redis服务作为主服务器替换上去。

    具体的替换计划是这样的:

    首先向B发送一个SAVE命令,让它创建一个最新的快照文件

    接着将这个快照文件发送给机器C,并在机器C上面启动Redis

    在机器B上执行slaveof 命令,让它将C机器作为主服务器



    主从复制开发运维常见问题

    一.读写分离:读流量分摊到从节点。

    可能遇到的问题:

    1.数据复制的延迟

    Redis 复制数的延迟由于异步复制特性是无法避免的,延迟取决于网络带宽和命令阻塞情况,对于无法容忍大量延迟场景,可以编写外部监控程序监听主从节点的复制偏移量,当延迟较大时触发报警或者通知客户端避免读取延迟过高的从节点,实现逻辑如下图:

    说明如下:

    1) 监控程序(monitor) 定期检查主从节点的偏移量,主节点偏移量在info replication 的master_repl_offset 指标记录,从节点 偏移量可以查询主节点的slave0 字段的offset指标,它们的差值就是主从节点延迟的字节量。

    2)当延迟字节量过高时,比如超过10M。监控程序触发报警并通知客户端从节点延迟过高。可以采用Zookeeper的监听回调机制实现客户端通知。

    3) 客户端接到具体的从节点高延迟通知后,修改读命令路由到其他从节点或主节点上。当延迟恢复后,再次通知客户端,恢复从节点的读命令请求。

    这种方案成本较高,需要单独修改适配Redis的客户端类库。

    2.读到过期数据

    当主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维护过期数据删除策略,删除策略主要有两种:惰性删除和定时删除。

    惰性删除:主节点每次处理读取命令时,都会检查key是否超时,如果超时则执行del命令删除key对象那个,之后del命令也会异步 发送给 从节点

    需要注意的是为了保证复制的一致性,从节点自身永远不会主动删除超时数据,如上图。

    定时删除:

    Redis主节点在内部定时任务会循环采样一定数量的key,当发现采样的key过期就执行del命令,之后再同步给从节点,如下图

    如果此时 数据的大量超时,主节点采样速度跟不上过期速度且主节点没有读取过期key的操作,那么从节点将无法收到del命令,这时在从节点 上可以读取到已经超时的数据。Redis在3.2 版本解决了这个问题,从节点 读取数据之前会检查键的过期时间来决定是否返回数据,可以升级到3.2版本来规避这个问题。

    3.从节点故障问题

    对于从节点故障问题,需要在客户端维护可用从节点列表,当从节点故障时立刻切换到其他从节点或主节点上。

    建议大家在做读写分离时,可以考虑使用Redis Cluster(分布式),Twemproxy(代理分片,Codis集群等分布式解决方案


    二.主从配置不一致

    1. max memory配置不一致:这个会导致数据的丢失。

    原因:例如master配置4G,slave配置2G,这个时候主从复制可以成功,但如果在进行某一次全量复制的时候,slave拿到master的RDB加载数据时发现自身的2G内存不够用,这时就会触发slave的maxmemory策略,将数据进行淘汰。更可怕的是,在高可用的集群环境下,如果将这台slave升级成master的时候,就会发现数据已经丢失了,而且无法挽回了,这个有些时候会出现一些诡异的情况,当然,我们可以使用一些标准的工具进行一个安装,并且对其进行一个监控。

    2. 数据结构优化参数不一致(例如hash-max-ziplist-entries):这个就会导致内存不一致。

    原因:例如在master上对这个参数进行了优化,而在slave没有配置,就会造成主从节点内存不一致的诡异问题。

    三.规避全量复制

    1.第一次全量复制

    当某一台slave第一次去挂到master上时,是不可避免要进行一次全量复制的,那么如何去想办法降低开销呢?

    方案1:小主节点,例如把redis分成2G一个节点,这样一来会加速RDB的生成和同步,同时还可以降低fork子进程的开销(master会fork一个子进程来生成同步需要的RDB文件,而fork是要拷贝内存快的,如果主节点内存太大,fork的开销就大)。

    方案2:既然第一次不可以避免,那可以选在集群低峰的时间(凌晨)进行slave的挂载。

    2.节点运行ID(run_id)不匹配

    例如主节点重启(RunID发生变化),对于slave来说,它会保存之前master节点的RunID,如果它发现了此时master的RunID发生变化,那它会认为这是master过来的数据可能是不安全的,就会采取一次全量复制。

    方案1:对于这类问题,只有是做一些故障转移的手段,例如master发生故障宕掉,选举一台slave晋升为master(哨兵或集群)。

    方案2:Redis提供了debug reload的重启方式:重启后,主节点的runid和offset都不受影响,避免了全量复制。

    3.复制积压缓冲区不足

    复制缓冲区空间不足,比如默认值1M,可以部分复制。但如果缓存区不够大的话,首先需要网络中断,部分复制就无法满足。其次需要增大复制缓冲区配置(relbacklogsize),对网络的缓冲增强。

    master生成RDB同步到slave,slave加载RDB这段时间里,master的所有写命令都会保存到一个复制缓冲队列里(如果主从直接网络抖动,进行部分复制也是走这个逻辑),待slave加载完RDB后,拿offset的值到这个队列里判断,如果在这个队列中,则把这个队列从offset到末尾全部同步过来,这个队列的默认值为1M。而如果发现offset不在这个队列,就会产生全量复制。

    方案1:增大复制缓冲区的配置 rel_backlog_size 默认1M,我们可以设置大一些,从而来加大offset的命中率。这个值,可以假设,一般网络故障时间是分钟级别,那可以根据当前的QPS来算一下每分钟可以写入多少字节,再乘以可能发生故障的分钟就可以得到我们这个理想的值。

    四.规避复制风暴

    什么是复制风暴?举例:master重启,其master下的所有slave检测到RunID发生变化,导致所有从节点向主节点做全量复制。尽管redis对这个问题做了优化,即只生成一份RDB文件,但需要多次传输,仍然开销很大。

    1.但主节点复制风暴:主节点重启,多从节点全量复制

    解决方案:更换复制拓扑,如下图:

    1.将原来master与slave中间加一个或多个slave,再在slave上加若干个slave,这样可以分担所有slave对master复制的压力。(这种架构还是有问题:读写分离的时候,slave1也发生了故障,怎么去处理?)

    2.如果只是实现高可用,而不做读写分离,那当master宕机,直接晋升一台slave即可。

    2.单机器复制风暴:机器宕机后的大量全量复制,如下图:

    方案1:应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点。

    当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密集的全量复制

    方案2:还有我们可以采用高可用手段(slave晋升master)就不会有类似问题了。


    Redis常见性能问题和解决办法

    1)Master写内存快照

    save命令调度rdbSave函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以Master最好不要写内存快照。

    2)Master AOF持久化

    如果不重写AOF文件,这个持久化方式对性能的影响是最小的,但是AOF文件会不断增大,AOF文件过大会影响Master重启的恢复速度。

    3)Master调用BGREWRITEAOF

    Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。

    下面是我的一个实际项目的情况,大概情况是这样的:一个Master,4个Slave,没有Sharding机制,仅是读写分离,Master负责 写入操作和AOF日志备份,AOF文件大概5G,Slave负责读操作,当Master调用BGREWRITEAOF时,Master和Slave负载会 突然陡增,Master的写入请求基本上都不响应了,持续了大概5分钟,Slave的读请求过也半无法及时响应,Master和Slave的服务器负载图 如下:

    Master Server load:

    Slave server load:

    上面的情况本来不会也不应该发生的,是因为以前Master的这个机器是Slave,在上面有一个shell定时任务在每天的上午10点调用 BGREWRITEAOF重写AOF文件,后来由于Master机器down了,就把备份的这个Slave切成Master了,但是这个定时任务忘记删除 了,就导致了上面悲剧情况的发生,原因还是找了几天才找到的。

    将no-appendfsync-on-rewrite的配置设为yes可以缓解这个问题,设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入。最好是不开启Master的AOF备份功能。

    4)Redis主从复制的性能问题

    第一次Slave向Master同步的实现是:Slave向Master发出同步请求,Master先dump出rdb文件,然后将rdb文件全量 传输给slave,然后Master把缓存的命令转发给Slave,初次同步完成。第二次以及以后的同步实现是:Master将变量的快照直接实时依次发 送给各个Slave。不管什么原因导致Slave和Master断开重连都会重复以上过程。Redis的主从复制是建立在内存快照的持久化基础上,只要有 Slave就一定会有内存快照发生。虽然Redis宣称主从复制无阻塞,但由于Redis使用单线程服务,如果Master快照文件比较大,那么第一次全 量传输会耗费比较长时间,且文件传输过程中Master可能无法提供服务,也就是说服务会中断,对于关键服务,这个后果也是很可怕的。

    以上1.2.3.4根本问题的原因都离不开系统IO瓶颈问题,也就是硬盘读写速度不够快,主进程 fsync()/write() 操作被阻塞。

    5)单点故障问题

    由于目前Redis的主从复制还不够成熟,所以存在明显的单点故障问题,这个目前只能自己做方案解决,如:主动复制,Proxy实现Slave对 Master的替换等,这个也是目前比较优先的任务之一。

    简单总结:

    -  Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。

    -  如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。

    -  为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。

    -  尽量避免在压力较大的主库上增加从库

    -  为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系 为:Master<–Slave1<–Slave2<–Slave3…….,这样的结构也方便解决单点故障问题,实现Slave对 Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

    相关文章

      网友评论

          本文标题:Redis复制的原理和优化

          本文链接:https://www.haomeiwen.com/subject/fqhfwhtx.html