💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 前言 主从复制是哨兵`sentinel`和`cluster`的基础,知其然知其所以然,所以,了解了其中的细节原理和每一步都执行了什么操作,对以后的调试也是很有帮助的。 ### 为什么要使用主从 在实际的场景当中单一节点的`redis`容易面临风险,比如只有一台`redis`,然后挂了,本来要经过`缓存层`的东西直接把压力转到`数据库`上了,即使数据库扛得住,响应速度可能也不是很快,这样业务受影响了,如果有主从复制,一台从机挂了还可以在其他从机获取数据,所以,主从复制可以很好的解决`单机器故障`的问题,也就是`高可用`问题,但是其实还存在`故障转移问题`,后面我会写文章解决这个。 ### 主从复制的简单流程 ![](https://img.kancloud.cn/7f/fc/7ffc9941697b807ffdf6d658b0775019_800x500.png) 1.这里我把一系列操作举例放到实际业务流程中,比如有`N`多用户在频繁的读取数据。 2.如果用户1先去`redis-slave`缓存里取数据。 3.缓存中没有数据,去数据库里取。 4.`database proxy`分发读取数据操作,请求落在`mysql`从节点上。 5.`database proxy`将获取到的数据(这里假如获取到了,没有获取到直接返回给app)写入`redis-master` 主节点。 6.同时把获取到的数据返回给用户。 7.后面的`N`个用户获取相同的数据,压力被分散在不同的`redis-slave`上面,即使,某一个`redis-slave`宕机了,其他`redis-slave`还可以继续提供服务。 **注:** 主从复制数据的复制是`单向的`,只能由主节点到从节点默认情况下,每台`redis`服务器都是主节点,也就是`主节点挂了其他从节点可以变成主节点`(这个切换过程我附在下面);且一个主节点可以有多个从节点(或没有从节点),但**一个从节点只能有一个主节点**。 ![](https://img.kancloud.cn/b2/0a/b20af9ddbeb43599e1c419e4373d0a85_800x360.png) ### 主从复制搭建 **从节点开启主从复制,有3种方式:** #### 1、配置文件启用 在从服务器的配置文件中加入:`slaveof masterip masterport` #### 2、启动命令启用 redis-server启动命令后加入 `--slaveof masterip masterport` #### 3、客户端命令启用 redis服务器启动后,直接通过客户端执行命令:`slaveof masterip masterport`,则该redis实例成为从节点。 **通过 `info replication` 命令可以看到复制的一些信息** 注 : `Log` 文件位置 `vi /var/log/redis/redis.log` ### 主从复制原理 主从复制过程大体可以分为3个阶段:**连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段。** 在从节点执行 `slaveof` 命令后,复制过程便开始运作,复制过程大致分为6个过程。 ![](https://img.kancloud.cn/30/aa/30aa75b57fa2cad54c1cda08f527d137_523x227.png) 对照日志看一下日志如下 ![](https://img.kancloud.cn/a8/09/a8096ac92a0aaf9672fea8ce6e4d83a0_554x155.png) `1`保存主节点(master)信息。 `2`从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接从节点会建立一个 `socket` 套接字,从节点建立了一个端口为`xxxxx`的套接字,专门用于接受主节点发送的复制命令。从节点连接成功后打印如下日志: ![](https://img.kancloud.cn/96/55/9655de9a9d6003545768c30d85e7de49_469x37.png) 如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行 `slaveof no one` 取消复制 关于连接失败,可以在从节点执行 `info replication` 查看 `master_link_down_since_seconds` 指标,它会记录与主节点连接失败的系统时间。从节点连接主节点失败时也会每秒打印如下日志,方便发现问题: Error condition on socket for SYNC: {socket_error_reason} `3`发送 `ping` 命令 连接建立成功后从节点发送 ping 请求进行首次通信,ping 请求主要目的如下: 检测主从之间网络套接字是否可用。 检测主节点当前是否可接受处理命令。 如果发送 ping 命令后,从节点没有收到主节点的 pong 回复或者超时,比如网络超时或者主节点正在阻塞无法响应命令,从节点会断开复制连接,下次定时任务会发起重连。 我估计好多人的学软件的第一课 都`ping pong`过把,这里就不解释了。。 `4`权限验证。如果主节点设置了 `requirepass` 参数,则需要密码验证,从节点必须配置 `masterauth` 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。 先`ping`服务器和端口,通了肯定校验一下密码 `5`同步数据集。主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作也是耗时最长的步骤。 `6`命令持续复制。当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。 ### 全量复制和部分复制和复制偏移量 #### 全量复制 用于初次复制或其它无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作,当数据量较大时,会对主从节点和网络造成很大的开销 ![](https://img.kancloud.cn/2e/1f/2e1f177b56845a77db0e8b4556630be9_800x611.png) `1.Redis` 内部会发出一个同步命令,刚开始是 `Psync` 命令,Psync ? -1表示要求 `master` 主机同步数据 `2.`主机会向从机发送 `runid` 和 `offset`,因为 `slave` 并没有对应的 `offset`,所以是`全量复制` `3.`从机 slave 会保存 主机master 的基本信息 `save masterInfo` `4.`主节点收到全量复制的命令后,执行`bgsave`(异步执行),在后台生成`RDB`文件(快照),并使用一个缓冲区(称为`复制缓冲区`)记录从现在开始执行的所有写命令 `5.`主机`send RDB` 发送 `RDB` 文件给从机 `6.`发送缓冲区数据 `7.`刷新旧的数据,从节点在载入主节点的数据之前要先将老数据清除 `8.`加载 `RDB` 文件将数据库状态更新至主节点执行`bgsave`时的数据库状态和缓冲区数据的加载 ##### 全量复制的开销主要如下 `1.`bgsave 时间 `2.`RDB 文件网络传输时间 `3.`从节点清空数据的时间 `4.`从节点加载 RDB 的时间 #### 部分复制 用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销,需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制 部分复制是 `redis 2.8` 以后出现的,之所以要加入部分复制,是因为全量复制会产生很多问题,比如像上面的时间开销大、无法隔离等问题, **`redis` 希望能够在 `master` 出现抖动(相当于断开连接)的时候,可以有一些机制将复制的损失降低到最低。** ![](https://img.kancloud.cn/49/7f/497fd38689c333a108cee546ae088ce3_725x462.png) `1.`如果网络抖动(连接断开 `connection lost`) `2.`主机`master` 还是会写 `replbackbuffer`(复制缓冲区) `3.`从机`slave` 会继续尝试连接主机 `4.`从机`slave` 会把自己当前 `runid` 和偏移量传输给主机 `master`,并且执行 `pysnc` 命令同步 `5.`如果 `master` 发现你的偏移量是在缓冲区的范围内,就会返回 `continue` 命令 `6.`同步了 `offset` 的部分数据,所以部分复制的基础就是偏移量 `offset`。 #### 复制偏移量 参与复制的主从节点都会维护自身复制偏移量。主节点(`master`)在处理完写入命令后,会把命令的字节长度做累加记录,统计信息在 `info relication` 中的 `master_repl_offset` 指标中: ![](https://img.kancloud.cn/b2/84/b284e9b12bb078ca3302d121ab3f4b07_311x173.png) 从节点(`slave`)每秒钟上报自身的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量,统计指标如下: ![](https://img.kancloud.cn/4a/e0/4ae0e74e0ebd6aabdb4575cb39a0f5c7_505x87.png) 从节点在接收到主节点发送的命令后,也会累加记录自身的偏移量。统计信息在 `info relication` 中的 `slave_repl_offset` 中 ### 复制积压缓冲区 复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为`1MB`,当主节点有连接的从节点(`slave`)时被创建,这时主节点(`master`)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。 ![](https://img.kancloud.cn/77/81/77812a8c38ba54bb4aca4375d4ce2b0d_485x228.png) 在`命令传播阶段`,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(`offset`) 。由于复制积压缓冲区定长且先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。可以对照全量复制`redis-master`的图理解 复制挤压缓冲区。 ### 正常情况下redis是如何决定是全量复制还是部分复制 1.从节点将`offset`发送给主节点后,主节点根据`offset`和缓冲区大小决定能否执行部分复制: 2.如果`offset`偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制; 3.如果`offset`偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。 #### 1、缓冲区大小调节: 由于缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点`offset`的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。反过来说,为了提高网络中断时部分复制执行的概率,可以根据需要增大复制积压缓冲区的大小(通过配置`repl-backlog-size`)来设置;例如如果网络中断的平均时间是`60s`,而主节点平均每秒产生的写命令(特定协议格式)所占的字节数为`100KB`,则复制积压缓冲区的平均需求为`6MB`,保险起见,可以设置为`12MB`,来保证绝大多数断线情况都可以使用部分复制。 #### 2、服务器运行ID(`runid`) 每个`redis`节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个`redis`节点。 通过`info server`命令,可以查看节点的`runid`: ![](https://img.kancloud.cn/15/4e/154e8828f9dc14c4ded35327fceeb956_356x321.png) 主从节点初次复制时,主节点将自己的`runid`发送给从节点,从节点将这个`runid`保存起来;当断线重连时,从节点会将这个`runid`发送给主节点;主节点根据`runid`判断能否进行部分复制: 如果从节点保存的`runid`与主节点现在的`runid`相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看`offset`和复制积压缓冲区的情况) 如果从节点保存的`runid`与主节点现在的`runid`不同,说明从节点在断线前同步的`Redis`节点并不是当前的主节点,只能进行全量复制。 ### 主从复制进阶常见问题解决 #### 1、读写分离 读流量分摊到从节点。这是个非常好的特性,如果一个业务只需要读数据,那么我们只需要连一台 `slave` 从机读数据。 ![](https://img.kancloud.cn/c0/c6/c0c61fe7340e2cc9c8c5fdfa4a73aedf_438x115.png) 虽然读写有优势,能够让读这部分分配给各个 slave 从机,如果不够,直接加 `slave` 机器就好了。但是也会出现以下问题。 **1.复制数据延迟。** 可能会出现 `slave` 延迟导致读写不一致等问题,当然你也可以使用监控偏移量 offset,如果 offset 超出范围就切换到 `master` 上,逻辑切换,而具体延迟多少,可以通过 `info replication` 的 `offset` 指标进行排查。 对于无法容忍大量延迟场景,可以编写外部监控程序监听主从节点的复制偏移量,当延迟较大时触发报警或者通知客户端避免读取延迟过高的从节点 同时从节点的`slave-serve-stale-data`参数也与此有关,它控制这种情况下从节点的表现:如果为`yes`(默认值),则从节点仍能够响应客户端的命令;如果为`no`,则从节点只能响应`info`、`slaveof`等少数命令。该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为`no`。 **2.从节点故障问题** 对于从节点的故障问题,需要在客户端维护一个可用从节点可用列表,当从节点故障时,立刻切换到其他从节点或主节点。 #### 2、主从配置不一致 主机和从机不同,经常导致主机和从机的配置不同,并带来问题。 数据丢失:主机和从机有时候会发生配置不一致的情况,例如 `maxmemory` 不一致,如果主机配置 `maxmemory` 为`8G`,从机 `slave` 设置为`4G`,这个时候是可以用的,而且还不会报错。但是如果要做高可用,让从节点变成主节点的时候,就会发现数据已经丢失了,而且无法挽回。虽然错误很低级,但是有人会犯。。。 #### 3、规避全量复制 全量复制指的是当 `slave` 从机断掉并重启后,`runid` 产生变化而导致需要在 `master` 主机里拷贝全部数据。这种拷贝全部数据的过程非常耗资源。全量复制是不可避免的,例如第一次的全量复制是不可避免的,这时我们需要选择小主节点,且maxmemory 值不要过大,这样就会比较快。同时选择在低峰值的时候做全量复制。 ##### 1.造成全量复制的原因 ##### 1、是主从机的运行 `runid` 不匹配。 解释一下,主节点如果重启,`runid` 将会发生变化。如果从节点监控到 `runid` 不是同一个,它就会认为你的节点不安全。当发生故障转移的时候,如果主节点发生故障,那么从机就会变成主节点。我们会在后面讲解哨兵和集群。 ##### 2、复制缓冲区空间不足 比如默认值`1M`,可以部分复制。但如果缓存区不够大的话,首先需要网络中断,部分复制就无法满足。其次需要增大复制缓冲区配置(`relbacklogsize`),对网络的缓冲增强。参考之前的说明。 **解决办法:** 在一些场景下,可能希望对主节点进行重启,例如主节点内存碎片率过高,或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点,会使得runid发生变化,可能导致不必要的全量复制。 为了解决这个问题,rdis提供了debug reload的重启方式:重启后,主节点的`runid`和`offset`都不受影响,避免了全量复制。 ##### 3、`master` 主机挂了重启 当一个主机下面挂了很多个 `slave` 从机的时候,主机 `master` 挂了,这时 `master` 主机重启后,因为 `runid` 发生了变化,所有的 `slave` 从机都要做一次全量复制。这将引起单节点和单机器的复制风暴,开销会非常大。 **解决办法:** 可以采用树状结构降低多个从节点对主节点的消耗 从节点采用树状树非常有用,网络开销交给位于中间层的从节点,而不必消耗顶层的主节点。但是这种树状结构也带来了运维的复杂性,增加了手动和自动处理故障转移的难度 #### 4、单机器的复制风暴 由于 `redis`的单线程架构,通常单台机器会部署多个 `redis` 实例。当一台机器(`machine`)上同时部署多个主节点(`master`)时,如果每个 `master` 主机只有一台 `slave` 从机,那么当机器宕机以后,会产生大量全量复制。这种情况是非常危险的情况,带宽马上会被占用,会导致不可用。 **解决办法:** 应该把主节点尽量分散在多台机器上,避免在单台机器上部署过多的主节点。 当主节点所在机器故障后提供故障转移机制,避免机器恢复后进行密集的全量复制。