Administrator
发布于 2022-12-23 / 70 阅读
0
0

Redis主从复制

主从复制的理解

在学习MySQL的时候其实就已经接触到了主从复制的概念了,只是现在来说,一个是作用在内存,一个作用在磁盘。

然后说回来——为什么要主从复制呢?

解决单点问题(单点问题只某一个节点出现问题后,会导致整个系统出现问题),那么对于数据库和缓存
来说,要是只有一个数据库和一个缓存,先不讨论数据多少的问题;要是某时候,数据库或者缓存挂掉了,
是不是会对系统有大影响(数据库肯定会,缓存不一定,因为如果是少请求的情况下直接请求
数据库也不是不行,只是那时候也没必要上缓存,要解决单点问题的一般都是分布式系统)。

那么如何解决单点问题呢?----冗余
其实就是凡事留一手,把数据复制多个副本部署到其他机器,主就是原机器,从就是副本了。
在原机器出问题的时候,副本还能顶上去,防止系统不可用。

在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。实现的操作就是主从复制。

主从复制实现

参与复制的Redis实例划分为主节点(master)和从节点(slave)。默认情况下,Redis都是主节点。每个从节点只能有一个主节点,而主节点可以同时具有多个从节点。

复制建立与断开

建立

1)在配置文件中加入slaveof {masterHost} {masterPort}随Redis启动生效。
2)在redis-server启动命令后加入--slaveof {masterHost} {masterPort}生效。
3)直接使用命令:slaveof {masterHost} {masterPort}生效。

可以得知:slaveof命令在使用时,可以运行期动态配置,也可以提前写到配置文件中。
使用replicaof命令(Redis 5.0 之前使用 slaveof)也可以(slaveof应该是全通用,
replicaof能不能兼容之前就得测试了)


salveof命令还能执行切主操作,就是如果有主节点了,那么久切主,没有那就是建立主从。
但要注意的是,无论是切主还是建立主从,都会使数据被清空然后重新写入主节点的数据

断开

从节点执行slaveof no one命令

注:
从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化。

读写分离

默认情况下,从节点使用slave-read-only=yes配置为只读模式。由于复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点会造成主从数据不一致。

所以在主从模式中,读操作主从节点都能处理;但写操作由主节点负责,然后同步操作到从节点。

问题:

为什么从节点只能读?
因为都能写的话会造成数据不一致问题,最后要进行同步。

主节点不也是要同步到从节点嘛?
如果在并发的情况下,对一个数据修改了两次,但发生在不同的从节点,你怎么保证最后的结果符合要求呢?
比如两次加1操作,最后可能会出现只加了1的情况。这时候你想解决问题那就得涉及到加锁等操作了。
但只有主节点执行写操作就不会有这样的问题,虽然同步还是要同步的。

主从同步

复制过程

从节点执行slaveof命令后,复制完整流程如下:

1、保存主节点(master)信息
执行slaveof后从节点保存主节点的地址信息便直接返回

2、从节点尝试与该节点建立socket连接
从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,
会尝试与该节点建立网络连接。(从节点会建立一个socket套接字,专门用于接受主节点发送的复制命令)
如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行slaveof no one取消复制

3、发送ping命令
连接建立成功后从节点发送ping请求进行首次通信。
发送ping命令后,从节点没有收到主节点的pong回复或者超时,比如网络超时或者主节点正在阻塞无法响应
命令,从节点会断开复制连接,下次定时任务会发起重连

4、权限验证
如果主节点设置了requirepass参数,则需要密码验证

5、数据同步
主从连接正常通信后,进行全量复制/部分复制

6、命令持续复制
之后主节点有修改的数据也是要同步到从节点的,所以会持续的给从节点发送命令,保证数据一致性

简单理解归为三步骤就行:

1、建立连接
2、数据同步
3、持续复制

数据同步

Redis在2.8及以上版本使用psync命令完成主从数据同步。数据同步又分为:全量复制和部分复制。

psync命令

从节点使用psync命令完成部分复制和全量复制功能,命令格式:psync {runId} {offset},参数含义如下:

runId:从节点所复制主节点的运行id。
每个Redis节点启动后都会动态分配一个40位的十六进制字符串作为运行ID。
运行ID的主要作用是用来唯一识别Redis节点。

offset:当前从节点已复制的数据偏移量。
参与复制的主从节点都会维护自己的数据偏移量,数据偏移量就是**命令的字节长度累加**。
从节点也会按时上报自己的偏移量给主节点,所以主节点会有从节点的偏移量。
从节点对主节点的命令进行同步后,也会累加自身的偏移量

复制时还有另一个重要组件:复制积压缓冲区(repl_backlog_buffer)

《Redis开发与运维》中说是队列;《Redis核心技术与实战》说是环形缓存区。

repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。主节点(master)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。用于部分复制和复制命令丢失的数据补救

发送命令后的响应:

回复+FULLRESYNC {runId} {offset},那么从节点将触发全量复制流程。
回复+CONTINUE,从节点将触发部分复制流程。
回复+ERR,说明主节点版本低于Redis2.8,无法识别psync命令,从节点将发送旧版的sync命令触发全量复制流程。

全量复制

一般用于初次复制场景,会把主节点全部数据一次性发送给从节点,当数据量较大时,会对主从节点和网络造成很大的开销。

执行流程:

全量复制

replication buffer(复制缓冲区)

在做读写分离的场景时,从节点也负责响应读命令。如果此时从节点正出于全量复制阶段或者复制中断,那么从节点在响应读命令可能拿到过期或错误的数据。
Redis复制提供了slave-serve-stale-data参数,默认开启状态。如果开启则从节点依然响应所有命令。可以设置关闭

全量复制的时间开销

主节点bgsave时间。
RDB文件网络传输时间。
从节点清空数据时间。
从节点加载RDB的时间。
可能的AOF重写时间。
为什么是RDB

执行复制流程数据的传输为什么用RDB?用AOF不行吗?

这里直接比较RDB和AOF的优点就行

RDB文件比AOF的小,在启动时执行起来比较快,且小文件在数据传输时会减少传输时间

部分复制

用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。

执行流程:

部分复制

这里会有个问题:
复制积压缓存区是一个环形缓冲区(队列也好),都会有一个问题:如果在复制中断期间,主节点的
写入已经超过了缓冲区的大小了,没同步的数据又被覆盖了。那么就算重连了之后,从节点将自己之前复制
到的偏移量位置发送过来,也已经找不到啦,我图中也没画出这时候会怎么办,
因为书中也没说,只能以后考证。

对此,书中只给出了避免情况,没有介绍到Redis的操作:

设置成缓冲空间大小尽量大
考虑使用切片集群来分担单个主库的请求压力。

补:
在《Redis开发与运维》中提到
如果请求的偏移量不在主节点的积压缓冲区内,则无法提供给从节点数据,因此部分复制会退化为全量复制

为什么要复制积压缓冲区

有时候可能会觉得很奇怪,既然你replication buffer能记录命令,为什么还要一个复制积压缓冲区呢?

以下细节没考究,只是看到了一篇文章和我想的一样就记录下来的,虽然没考究,但感觉非常合理:

一开始的理解的是replication buffer是公用的,一直写入命令的,但我没考虑到的是:一个主节点有多个从节点,每个从节点进行复制的时机不是一样的。所以某一个节点在复制时记录在replication buffer命令对某一个节点来说会是已经存在了的,但好像也没影响呀,因为都记录有偏移量。但不同的从节点全量复制的时机不同,公用的话那有些命令可能是别的节点已经同步了的,这样也会写入进去,这合理嘛?

然后就是另一个问题——replication buffer是否一直写入命令?
不是,replication buffer只在全量复制的时候才会写入,那么问题就好说了,为啥不能用replication buffer,因为数据不全呀。

这么来看repl backlog buffer还是有必要的。所以有些事知道原理确实好解决。

image-1671871917620

补文章:https://juejin.cn/post/7024775438526316552

心跳检测

主从节点在建立复制后,它们之间维护着长连接并彼此发送心跳命令。

主从节点彼此都有心跳检测机制,各自模拟成对方的客户端进行通信

主节点默认每隔10秒对从节点发送ping命令,判断从节点的存活性和连接状态。频率可调。
从节点在主线程中每隔1秒发送replconf ack {offset}命令,给主节点上报自身当前的复制偏移量。

主节点根据replconf命令判断从节点超时时间,体现在info replication统计中的lag信息中,
lag表示与从节点最后一次通信延迟的秒数,正常延迟应该在0和1之间。
如果超过repl-timeout配置的值(默认60秒),则判定从节点下线并断开复制客户端连接。
主节点判定从节点下线后,如果从节点重新恢复,心跳检测会继续进行

lag

读写分离的问题

对于读占比较高的场景,可以通过把一部分读流量分摊到从节点(slave)来减轻主节点(master)压力,同时需要注意永远只对主节点执行写操作

从节点响应读操作的问题:

1、复制数据延迟

主节点把写命令同步给从节点。写命令的发送过程是异步完成,也就是说主节点自身处理完写命令后直接返回给客户端,并不等待从节点复制完成。所以复制延迟的问题是无法避免的,延迟取决于网络带宽和命令阻塞情况。
业务中如果不能容许大延迟问题,可以编写监控程序对主从复制偏移量进行监控

2、读过期数据

主节点存储大量设置超时的数据时,如缓存数据,Redis内部需要维护过期数据删除策略,删除策略主要有两种:惰性删除和定时删除。主节点的数据更新不及时,那么从节点的数据也是过期的。

惰性删除

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

定时删除

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

Redis在3.2版本中,从节点读取数据之前可以检查键的过期时间来决定是否返回数据,防止因延迟或阻塞无法及时接收del命令。

3、从节点故障问题

从节点的故障问题,需要在客户端维护可用从节点列表,当从节点故障时立刻切换到其他从节点或主节点上。类似上文提到的针对延迟过高的监控处理,需要开发人员改造客户端类库。

复制风暴

复制风暴是指大量从节点对同一主节点或者对同一台机器的多个主节点短时间内发起全量复制的过程。复制风暴对发起复制的主节点或者机器造成大量开销,导致CPU、内存、带宽消耗。

单主节点复制风暴

这种情况指的是一个主节点下挂载了多个从节点,主节点重启恢复的时候,多个从节点一起向主节点发起复制同步。(会有生成RDB消耗;传输RDB带宽消耗等问题短时间内爆发)

解决方案首先可以减少主节点(master)挂载从节点(slave)的数量,或者采用树状复制结构,加入中间层从节点用来保护主节点

单机器复制风暴

由于Redis的单线程架构,通常单台机器会部署多个Redis实例。当一台机器上同时部署多个主节点(master)时。如果这台机器出现故障或网络长时间中断,当它重启恢复后,会有大量从节点(slave)针对这台机器的主节点进行全量复制,会造成当前机器网络带宽耗尽。

解决:

分散部署
提供故障转移机制,减少全量复制操作

拓扑

Redis的主从结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从、树状主从结构。

一主一从

最简单的复制拓扑结构,用于主节点出现宕机时从节点提供故障转移支持

在这个结构下:当主节点关闭持久化功能时,如果主节点脱机要避免自动重启操作。
因为主节点之前没有开启持久化功能自动重启后数据集为空,这时从节点如果继续复制主节点会
导致从节点数据也被清空的情况,丧失了持久化的意义

一主多从

一主多从结构(又称为星形拓扑结构)使得应用端可以利用多个从节点实现读写分离。对于读占比较大的场景,可以把读命令发送到从节点来分担主节点压力。同时在日常开发中如果需要执行一些比较耗时的读命令,如:keys、sort等。

多个从节点会导致主节点写命令的多次发送从而过度消耗网络带宽,同时也加重了主节点的负载影响服务稳定性。

树状主从

树状主从结构(又称为树状拓扑结构)使得从节点不但可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层,可以有效降低主节点负载和需要传送给从节点的数据量。

当主节点需要挂载多个从节点时为了避免对主节点的性能干扰,可以采用树状主从结构降低主节点压力

就是“主——从——从”结构,那样进行复制同步的操作的时候主节点就不用传输给过多的从节点,只要传给其中几个,让其中几个再传输给另外的从节点就行。

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


评论