Redis高可用之主从复制原理演进分析

乎语百科 357 0

在很久之前写过一篇 Redis 主从复制原理的简略分析,基本是一个笔记类文章。

一、什么是主从复制

1.1 什么是主从复制

主从复制,从名字可以看出,至少需要 2 台 Redis 服务器,一台叫主 Redis 服务器,一台叫从 Redis 服务器,也可以把他们叫做主节点(主 Redis 服务器)从节点(从 Redis 服务器)。然后把主 Redis 服务器上的数据复制到从 Redis 服务器上,这就是主从复制。后续也会源源不断的把数据从主节点复制到从节点。

1.2 怎么设置主从复制

怎么设置主 Redis 服务器,怎么设置从 Redis 服务器?

比如有 2 台 Redis 服务器,ip 分别为:192.168.1.100 和 192.168.1.101。

  • 第一种方法

设置方法:在 Redis 的配置文件 redis.conf 中配置:replicaof masterip masterport

比如将 192.168.1.100 这台服务器设置为主(master)服务器,那么就在服务器 192.168.1.101 的配置文件里设置如下:

replicaof 192.168.1.100 6379

然后重启服务器,这样主服务器就是 192.168.1.100,从服务器就是 192.168.1.101。

  • 第二种方法

还可以用 redis-cli 客户端连接到 192.168.1.101,然后执行命令 replicaof 192.168.1.100 6379。

这种方式如果从 Redis 重启后,主从关系就消失了。

  • 第三种方法

在 redis-server 启动参数中增加 --replicaof 192.168.1.100 6379 参数

说明:Redis 5.0 后,replicaof 命令已经替换了 slaveof 命令,但是为了兼容 slaveof 还是可以用。

一台主服务器也可以有多台从服务器,从服务器也可以有从服务器。

二、为什么要主从复制

主从复制后就有多份数据,相当于有多个副本,既是备份也是容灾。

为什么要有主从复制功能?

其实问的就是 Redis 主从复制有什么作用,带来了啥好处。

  • 负载均衡

数据量大的时候,为了减轻服务器压力,会用读写分离模式来分摊流量,主服务器负责写,从服务器负责读。当然主服务器也可以读。

  • 故障恢复

主服务器出现问题时候,从服务器还可以继续提供服务。并且也可以把从服务器提升为主服务器,这就是 Redis 的哨兵模式。

高可用的数据冗余方式。

  • 数据冗余

多了一份数据,故障了,就可以快速恢复数据。

三、怎么进行主从复制

主从数据同步就是把主服务器生成的 RDB 数据文件复制到从服务器上,然后解析 RDB 文件,在从服务器上生成对应的数据。或同步相关的命令。

3.1 主从复制同步的演进

在 Redis2.8 之前,都是全量数据复制。也就是说,断线重连后,也是重新全量复制数据。这种方式把很多原来同步过了的数据又重新同步一次,这种方式的数据同步效率太低。

在 Redis2.8 之后,增加了部分重同步模式,也就是增加增量数据同步,只同步需要同步的数据。这就改进了之前的数据同步模式。

什么时候进行全量数据同步?第一次数据同步时候就进行全量数据同步。有时主从数据不一致时也需要全量同步。

什么时候进行增量数据同步?比如断线重连后,就进行增量数据同步。

3.2 Redis2.8之前复制

Redis2.8 之前主从同步有 2 个部分:全量同步,命令传播。

全量同步:主从节点建立连接,主节点回复后,从节点向主节点发送 SYNC 命令,把从节点服务器状态更新到当前主节点服务器状态。主节点创建全量数据的 RDB 快照文件,然后发送给从节点,从节点加载 RDB 文件恢复对应的数据。主节点再继续发送复制过程中积压在缓冲区内的新增命令到从节点,使从节点的数据到达和主节点数据一致。

命令传播:主节点和从节点保持连接,主节点将继续向从节点发送命令流,保证主节点上的数据集发生了变更同样在从数据集上也发生变更。

流程图:

3.3 Redis2.8之后复制

以 redis6.0 版本来介绍。

Redis2.8 之后全量复制与上面(Redis2.8之前)复制步骤差不多,SYNC 命令变成了 PSYNC 命令,之后增加了部分重同步。部分重同步改进了之前的每次需要全量同步问题。

增加了部分重同步,这个要怎么做才能兼容之前的全量同步呢?怎么知道从库复制到哪儿了?第一个从库肯定要记录下从库复制到哪儿了,下次断线重连时就可以告诉主库该从哪个地方开始复制了。主库也要记录自己的一些复制信息。Redis 用了几个概念就把这些问题给解决了,Replication ID,offset,replication_backlog。

  • Replication ID:复制 ID。这是一个较大的随机字符串,标记一个给定的数据集。每个主节点都会用这个 Repli ID 来标识内部数据集,从节点 ID。当从节点加入时,这个 repli id 就初始化了。

  • offset:复制偏移量。每个主节点都有这个 offset 偏移量,主节点将自己产生的数据发送给从节点时,发送多少字节数据,自身 offset 就会增加多少。从节点也有自己 offset,从节点写入数据时,offset 也会增加。断线重连时,就可以知道从哪里开始同步了。offset 需配合下面的复制积压缓冲区工作。

  • replication_backlog:复制积压缓冲区。它是在主节点上的一个环形缓冲区,用来存储主节点向从节点传递的命令。它是大小固定,存储的命令有限,所有超出了就会删除。从节点进行增量同步时,主节点会根据 offset 从 replication_backlog 中拷贝从节点缺失的数据到从节点。

Replication ID, offset,这一对来标识数据集版本。

Redis2.8之后就是用上面这几个概念实现部分数据重同步的。从节点发送主节点的 replid 和从节点的一个 offset,主节点拿到这个replid 和自己的 replid 比较,如果是一样,并且这个 offset 也在 backlog 中能找到,那就可以可以进行部分重同步。

全量复制步骤

  1. 主从节点先建立连接

建立连接后,从节点使用命令 PSYNC <replid> <offset> 向主节点发起同步请求。如果主从节点是第一次复制,那么命令为 PSYNC ? -1,replid 为 ?,因为是第一次复制不知道主库的 replid。offset 为 -1,表示第一次复制。

主节点收到 PSYNC 命令后,会用 FULLRESYNC 命令响应,带上主节点的 replid 和 offset 返回给从库,从库会记录下这两个参数。便于以后判断是否需要部分重同步。

  1. 同步数据

主节点执行 bgsave 命令生成 RDB 文件,生成完后把文件发送给从节点,从节点加载 RDB 文件。这个过程中,主节点不会阻塞,依然会接收客户端的命令请求,当然,这些请求不会写在之前的 RDB 文件里,为了保持主从数据一致,这些命令会存储在 replication buffer 中,记录 RDB 文件后的所有写操作。

  1. 同步缓冲的命令数据

协商就是根据先前定义好步骤来发送相关命令,为同步做准备工作。有点协议的意思。步骤如下:

当主库把 RDB 文件传送给从节点完成后,就会把 replication buffer 中的写命令操作发送给从节点,从节点执行这些操作命令,主从节点同步完成。

  1. 命令传播

之后会继续向从节点发送主节点的操作命令,从节点执行这些命令,保持主从数据的一致。

上面是一个主体的同步步骤,更加详细的步骤要分析源码了。

发送步骤与 Redis2.8 之前全量同步没有多大区别。

部分数据同步

部分数据同步,解决的是主从节点在同步命令时候,网络断了在连上时,Redis2.8 之前会在全量同步数据,显然开销太大,不合理。能不能只把断线后的数据同步一份,而不是全量同步?

网络断线后,就有部分命令数据没有同步到从节点上去,那我们能不能保存这部分命令数据?重连后,将断开期间的这部分命令重新同步给从节点,这样就不需要全量同步。

Redis2.8 之后引入了 replication_backlog 复制积压缓冲区,前面有讲到这个概念。命令一方面会传输给从节点,另外还会记录在这个复制积压缓冲区里。Redis 使用一个环形缓冲区的结构保存最近的一些命令。在缓冲区中,对字节进行编号,这个编号在 Redis 中叫复制偏移量。

是否部分同步条件?

  • 从节点 replid 和 主节点的 replid 相同

  • 复制偏移量 offset 在复制积压缓冲区的 backlog_off 和 offset 范围之间。

如果满足上面的 2 个条件,就进行部分数据重同步。

四、Redis4.0的同源增量同步

先看两个问题

1.从节点重启后丢失了原主节点的节点编号和复制偏移量,这导致重启后需要全量复制,这个很好办,把这些信息保存下来

2.主从切换后,主节点信息变化了,导致从节点需要全量数据同步,这个也好办,只要能确认新主节点数据是从原主节点复制过来就可以了

Redis4.0 后,对 PSYNC 进行了改进,提出了同源增量复制解决方案,来解决上面提到的两个全量复制问题。

第1个:从节点重启后,需要跟主节点全量数据同步,为什么?本质原因,是从节点丢失了主节点的编号信息和偏移量信息。Redis4.0后,就把主节点的编号信息写入到 RDB 中持久化保存。

第2个:主从切换后,从节点需要和主节点全量同步,为什么?原因就是新的主节点不认识原来主节点的编号信息。切换后怎么才能识别到呢?Redis4.0 后,主从切换后,新的主节点会将先前的主节点信息记录下来,这样新主节点就知道自己原先数据是从哪个旧主节点同步来的,大家都是从同一个地方出来的,应该接受部分数据同步策略。

五、Redis6.0无盘同步复制

什么叫无盘?

原先的同步复制是通过 fork 一个子进程生成 RDB 快照文件,RDB 存储在磁盘上,然后传输 RDB 文件,从节点服务器在恢复 RDB 文件数据。

无盘,就是说不生成 RDB 文件,不通过 RDB 来传输数据。而是直接通过网络来传输数据。

怎么做到无盘呢?

Redis6.0 后,它也是先 fork 一个子进程,这个子进程 dump 数据,它通过管道回写给主节点,主节点在将数据发送给从节点,这样的过程就是无盘传输。

六、Redis7.0共享复制缓冲区

6.1 多从库时主库占用内存过多问题

(from: https://mp.weixin.qq.com/s/UlHksrqFq0yfKh1uMFvYNg 作者:shooterIT)

如图所示,对于 Redis 主库,当用户的写请求到达时,主库会将变更命令分别写入所有从库的缓冲区(OutputBuffer),以及复制积压缓冲区(ReplicationBacklog)。全量同步也会执行该逻辑。所以在全量同步阶段经常会触发 client-output-buffer-limit,主库断开与从库的连接,导致主从同步失败,甚至出现循环持续失败的情况。

所有从库的连接在主库上是独立的,也就是说每个从库 OutputBuffer 占用的内存空间也是独立的,那么主从复制消耗的内存就是所有从库缓冲区内存大小之和。如果我们设定从库的 client-output-buffer-limit 为 1GB,如果有三个从库,则在主库上可能会消耗 3GB 的内存用于主从复制。另外,真实环境中从库的数量不是确定的,这也导致 Redis 实例的内存消耗不可控。

from: https://mp.weixin.qq.com/s/UlHksrqFq0yfKh1uMFvYNg 作者:shooterIT

6.2 OutputBuffer 拷贝和释放的堵塞问题

  • ReplicationBacklog 的限制

  • OutputBuffer 拷贝和释放的堵塞问题

具体内容请看这里:https://mp.weixin.qq.com/s/UlHksrqFq0yfKh1uMFvYNg

6.3 解决方案:共享复制缓冲区

具体方案请看这里:https://mp.weixin.qq.com/s/UlHksrqFq0yfKh1uMFvYNg 作者:shooterIT

七、参考

标签: # Redis

留言评论

  • 这篇文章还没有收到评论,赶紧来抢沙发吧~