Redis主从复制

简介: Redis主从复制通过全量同步、命令传播与增量复制三大机制,保障高可用与数据一致性。主节点处理读写并同步数据,从节点分担读请求;借助repl_backlog_buffer环形缓冲区与偏移量机制,实现断线后高效增量恢复,有效避免单点故障与数据丢失。

为什么要有主从复制

如果数据都存在同一台服务器上,假设遇到以下两种情况的任意一种:

1.服务器宕机。在恢复期间,无法处理客户端请求;

2.服务器硬盘损坏,数据丢失;

都会造成一定的损失。因此,我们需要将数据备份到其它服务器上,这些服务器同样能够为客户端提供服务。这样一来,哪怕任意一台服务器出现了故障,还有其它服务器作为后备力量。

使用多台服务器后,又有新的问题:

1.如何同步数据,即保证数据一致性?

一台服务器接收到数据后,其它服务器是不知道的。如果不进行数据的同步,就会有两个问题:一是这台服务器出现故障后,数据就丢失了;二是客户端如果将请求打在了其它服务器上,那么客户端就无法获得它期望的数据。

2.任务怎么分配?

是每台服务器都既负责读又负责写,还是一部分负责读,另一部分负责写?

Redis提供了主从复制模式来解决上述问题。主从复制模式能保证数据的一致性,并且采用读写分离的方式完成了任务的分配。

主服务器 既负责读又负责写,当它处理写操作时会将写操作同步给 从服务器,就实现了数据的同步。从服务器一般只负责执行来自客户端的读操作,因为对于Redis来说,读操作发生的频率一般是比写操作更高的,另外还要执行从 主服务器 同步过来的写操作命令。

在上文中,我将“同步”一次加粗了,因为这个词说起来简单,但实际上并不简单。主服务器是如何将写操作同步给 从服务器的呢?在这个过程中,会不会遇到什么新问题呢?

建立主从关系

在同步之前,要处理一个最基本的问题:如何让两台互不关联的服务器相连?相连之后如何确定谁是主服务器,谁是从服务器?

我们可以使用replicaof命令建立两台服务器之间的主从关系。在Redis 5.0之前,使用slaveof命令可以达到相同效果。这个命令非常直观,我们知道slave在英语中表示“奴隶”,replica在英语中表示“复制品”,都有主从关系的含义在里面。

例如,我们现在希望让服务器B充当从服务器,让服务器A充当主服务器,那么我们可以在服务器B中执行如下命令:

replicaof <服务器A的IP地址><服务器A的Redis端口号>

执行完毕后,服务器B就成为了服务器A的从服务器,接下来进行第一次同步。

第一次同步

第一次同步可以分为三个阶段:

1.建立连接,协商同步

2.主服务器 同步数据给 从服务器

3.主服务器 发送新的写操作命令给从服务器

第一阶段:建立连接,协商同步

执行完replicaof命令后,从服务器会给主服务器发送psync命令,告诉主服务器,自己希望进行数据同步。

psync命令包含两个参数,分别是 runID 和 offset :

  • runID:每个Redis服务器启动时都会自动生产一个随机的ID来标识自己。第一次同步时,从服务器不知道主服务器的 runID,所以它将第一个参数设置为问号。
  • offset:这个单词在MySQL中的分页查询比较常见,表示偏移量。在这里表示复制进度,实际上也是偏移量。由于此时还没有进行复制(同步),因此从服务器将第二个参数设置为-1.

主服务器收到命令后,会响应 FULLRESYNC 给从服务器。并在响应命令中带上 从服务器 请求的两个参数:自己的runID 和当前的复制进度 offset. 从服务器 接收到响应后,会记录这两个参数的值。FULLRESYNC 表示采用全量复制的模式,即 主服务器会把所有的数据复制给从服务器。

第一阶段的工作到这里就完成了,总结起来就是为接下来的全量复制做准备。

第二阶段:主服务器 同步数据给 从服务器

主服务器执行 BGSAVE 命令生成 RDB 文件,然后把 RDB 文件发送给 从服务器。从服务器 接收到 RDB 文件之后,会清空当前的数据,接着载入 RDB 文件中的数据。

在上次讲解 RDB 与 AOF 的时候,我们知道 BGSAVE 命令是父进程fork出了一个子进程来异步执行生成 RDB 文件的工作,并不会阻塞 Redis 主线程与处理客户端的命令。但在这期间,写操作的命令并没有记录到通过 BGSAVE 命令生成的 RDB 文件当中,也就是说又出现了数据不一致的问题。

为了解决因为生成 RDB 文件期间又有新加入的写操作命令导致的数据不一致问题,主服务器会在下面三个时期将收到的 写操作命令写入到 repl_backlog_buffer 环形缓冲区中,再复制到 replication buffer 缓冲区中:

  • 主服务器 生成 RDB 文件期间
  • 主服务器 发送 RDB 文件期间
  • 从服务器 加载 RDB 文件期间

这并不难理解,因为在这三个阶段当中,主服务器对数据进行的写操作,从服务器 是无法感知的,因此必须要将这三个阶段的数据单独保存起来。

第三阶段:主服务器发送新的写操作命令给从服务器

从服务器 成功接收并加载 RDB 文件后,意味着主要工作就做完了,它会给主服务器返回一个确认消息,类似于TCP三次握手中的ACK报文。主服务器收到确认消息后,就将最后的工作交给从服务器,也就是把新增加的写操作命令给从服务器。主服务器会将replication buffer缓冲区里所记录的写操作命令发送给从服务器,从服务器 接收并执行这些新增加的写操作命令,这样数据就同步了。第一次同步也就完成了。

真的百分之百同步了吗?

:question: 如果在发送缓冲区的内容时,主服务器又执行了新的写操作,那不就又出现了数据不一致问题吗?

命令传播

从服务器 发送 psync 命令,主服务器响应后,凭什么二者就能传输 RDB 文件了呢?

实际上是因为二者建立了 TCP 连接。完成第一次同步后,双方会维护这个 TCP 长连接。之所以是长连接,目的是规避频繁建立和释放 TCP 连接所带来的开销。后续主服务器可以通过这个 TCP 连接将 RDB 文件以及replication buffer缓冲区中的写操作命令发送给从服务器,使得双方的数据库状态相同。这个过程称之为基于长连接的命令传播

增量复制

在上面我们抛出了一个问题,如果在传输 replication buffer 缓冲区的新的写操作命令时,主服务器又执行了新的写操作命令,岂不是又有数据不一致的问题?

此外,网络不可避免会有延迟,甚至有断开的情况。那如果主从服务器之间的网络断开了,即无法进行命令传播了,那不也会导致数据不一致的问题?

网络恢复之后,又要如何保证主从服务器的数据一致性呢?

其实不难想到,我们只要进行一次 全量复制,即把主服务器内存中的所有数据再次打包发送给从服务器。这就是 Redis 2.8之前的解决方案。

但是这并不是我们希望的解决方案,因为数据量可能非常庞大。我们希望主服务器知道哪些是新增加的写操作命令,也就是希望主服务器知道哪些是增量,然后把增量复制给从服务器即可。在 Redis 2.8 之后,网络断开又恢复之后,主从服务器会采用增量复制的方式继续同步。

增量复制可以分为三步:

  • 恢复网络后,从服务器 发送 psync 命令给主服务器,此时由于二者之间已经有过数据的复制,因此 offset 参数不是-1;
  • 主服务器收到命令后,响应 CONTINUE 命令,告诉从服务器,接下来采用增量复制的方式进行数据的同步;
  • 主服务器将增量部分发送给从服务器,后者接收并执行这些命令

那么,主服务器怎么知道哪些是增量呢?主要依靠两个关键:

  • repl_backlog_buffer:一个环形缓冲区,用于主服务器找到未同步的数据;
  • replication offset:标记上面那个缓冲区的同步进度。主服务器使用 master_repl_offset 标记自己写到的位置,从服务器通过 slave_repl_offset 标记自己读到的位置。主服务器根据二者之间的差值进行判断。

repl_backlog_buffer 是什么时候被填充的?

实际上,在主服务器进行命令传播时,不仅会将写命令发给从服务器,也会将其写入到 repl_backlog_buffer 缓冲区中,这也意味着该缓冲区存放着最近的写命令。

网络恢复后,从服务器通过 psync 命令将自己的复制偏移量 slave_repl_offset 发送给主服务器,主服务器根据自己的 master_repl_offset 与 slave_repl_offset 之间的差距。

  • 如果主服务器发现,从服务器 要读取的数据还在环形缓冲区内,就采用增量同步的方式;
  • 反之,采用全量同步的方式。因为此时,从服务器 想要的数据已经不在环形缓冲区内了,必须要复制内存中的所有数据。
  • 主服务器在环形缓冲区内找到增量后,就会将其复制到 replication buffer 缓冲区中。实际上在第一次同步的第一阶段中我们也可以发现,replication buffer 的作用就是缓存将要传播给从服务器的命令,只是一个临时中转站,数据不会久留在其中。环形缓冲区类似于你家的冰箱,replication buffer 缓冲区类似于出门购物的购物袋。

为什么会有 从服务器 想要的数据不在环形缓冲区内的情况?实际上并不难理解,环形队列是有容量限制的,达到上限后就会进行数据的覆盖。所以为了避免频繁使用全量同步的方式,我们应该让 repl_backlog_buffer 缓冲区的容量尽可能大一点,其理想值size为:
$$ size=second \times WriteSizePerSecond $$

  • second:断连后重新连接所需要的平均时间(单位:秒)
  • WriteSizePerSecond:主服务器平均每秒产生的写命令数据量

解释完两个参数的含义后,就很好理解了。例如主服务器每秒产生2MB的写命令,从断连到重新连接平均需要10秒钟,那么 repl_backlog_buffer 的大小就不能低于20MB,另外为了保险起见,会将其设置为2倍,也就是40MB.

在配置文件中,修改 repl_backlog_size 参数即可修改环形缓冲区大小。

上面我们说的都是网络断连的情况,回到之前的问题:

:question: 如果在发送缓冲区的内容时,主服务器又执行了新的写操作,那不就又出现了数据不一致问题吗?

实际上,这些新的命令会写入到 repl_backlog_buffer 这个环形缓冲区内,然后跟着 replication buffer 中的命令一起通过 TCP 连接发送给从服务器。

那么新的问题来了,为什么我们要将新的命令先写入到repl_backlog_buffer 缓冲区内,再复制到 replication buffer 中呢?不能直接将新的命令放到 replication buffer 中吗?进一步再想想,为什么我们非得需要一个repl_backlog_buffer呢?

我们假设现在只有 replication buffer,假设它是一个长度固定的数组,因为如果长度可以无限长,那么已经被同步的数据就没办法移除了,就会占用很多内存。在生成、发送、载入 RDB 文件期间,新的写操作来了,主服务器将其保存在replication buffer中,然后发送给从服务器。发送,不能是直接将数组中的数据移出来,而是要复制一份再发送。因为如果在传输过程中丢包了,这个数据就再也找不到了。也就是说,在这个过程中,你必须要有一个地方存放复制出来的这个数据。那就又相当于你还是需要两个地方来存储,一个用来长期存储,一个用来存放临时的副本。这也是为什么我们需要一个repl_backlog_buffer用来长期存储,replication buffer用来临时存储。

但是又有新问题了,凭什么复制出来之后非得要保存到一个地方呢?复制出来不能直接发送吗?那不就不需要中间这个存放副本的地方了吗?

因为Redis是单线程的,所以 从服务器 加载RDB会阻塞,不能处理新命令。新命令到达 从服务器 之后也只会阻塞,实际上也就是换了个地方缓存。

那为什么不能直接缓存到从服务器呢?

由于阻塞,从服务器 Redis 不去读 socket ,导致 socket 接收缓冲区满了,TCP 窗口收缩为0,从服务器 TCP 协议栈向主服务器发送 窗口大小=0 的 ACK 报文,告诉他:哥们别发了,我要忙不过来了。而主服务器还想继续发送,但它发现对面窗口大小已经为0了,send()系统调用会阻塞,或者返回EAGAIN进行重试。但Redis是单线程的,线程就会卡在write或者send上,就无法处理客户端命令了。

分摊主服务器的压力

如果从服务器数量太多,每次主服务器都要与从服务器进行全量同步的话,就会有两个问题:

  • BGSAVE是父进程通过fork出子进程来生成 RDB 文件的,fork是阻塞操作,虽然阻塞时间不长。但如果主服务器数据非常庞大,加上从服务器数量也很庞大,就会导致主服务器花费大量时间阻塞在fork上,进而导致Redis无法处理正常请求;
  • 传输 RDB 文件会占用主服务器的网络带宽,对主服务器的响应造成影响;

因此,主服务器作为老板,在员工数量倍增时,应当优化组织架构,设置经理、主管等职位,减轻管理压力。从服务器也可以有自己的从服务器,它不仅接收来自主服务器同步过来的数据,也可以作为主服务器将自己的数据同步给自己的从服务器。

操作依然和建立连接时一样:

replicaof <目标服务器的IP地址><目标服务器的Redis端口号>

这样一来,就可以指定目标服务器充当自己的主服务器了。

总结

主从复制的三种模式:全量复制、基于长连接的命令传播、增量复制。

建立连接后,由于双方完全不了解彼此,因此要进行全量复制。在全量复制中,生成 RDB 文件和发送 RDB 文件都是耗时的操作。当从服务器数量较多,应当让一些从服务器也拥有自己的从服务器,分摊主服务器的压力,让其能够专注于处理客户端的命令。在全量复制的过程中,如果有新的写操作命令,会先放到 repl_backlog_buffer 缓冲区中,再复制一份到 replicate buffer 缓冲区中,然后再发送给从服务器。

第一次同步完成后,双方通过基于 TCP 长连接的命令传播,实现数据的同步。数据不仅会发送给从服务器,也会存放在 repl_backlog_buffer 环形缓冲区中。

如果期间出现了网络断连,就需要增量复制。主服务器通过自己的写偏移量与从服务器通过 psync 命令传递的读偏移量,判断 从服务器请求的内容,即增量是否在环形缓冲区中。如果不在,就进行全量复制;如果在,就将增量复制给replication buffer,再发送给从服务器。

为了避免断连后频繁使用全量复制,应当调大环形缓冲区的容量,即 repl_backlog_size 参数。

目录
相关文章
|
5天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10725 63
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
5天前
|
人工智能 IDE API
2026年国内 Codex 安装教程和使用教程:GPT-5.4 完整指南
Codex已进化为AI编程智能体,不仅能补全代码,更能理解项目、自动重构、执行任务。本文详解国内安装、GPT-5.4接入、cc-switch中转配置及实战开发流程,助你从零掌握“描述需求→AI实现”的新一代工程范式。(239字)
3093 126
|
1天前
|
人工智能 自然语言处理 供应链
【最新】阿里云ClawHub Skill扫描:3万个AI Agent技能中的安全度量
阿里云扫描3万+AI Skill,发现AI检测引擎可识别80%+威胁,远高于传统引擎。
1196 1
|
11天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
2558 6
|
25天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
24373 122

热门文章

最新文章