Redis 主从复制
Redis 核心机制解析:深入理解主从复制
在现代分布式架构中,单点故障是系统可靠性的最大威胁之一。为了解决这个问题,一个常见的实践是将数据复制多个副本并部署到不同的服务器上,这不仅提供了故障恢复的能力,还能通过负载均衡大幅提升系统的整体性能与吞吐量。作为高性能键值数据库的佼佼者,Redis 同样内置了强大而灵活的复制(Replication) 功能,允许用户创建包含相同数据的多个副本。
Redis 的复制功能是其高可用架构的基石。我们日常所使用的哨兵(Sentinel) 和集群(Cluster) 模式,都是在复制的基本机制之上构建而来的。可以说,深入理解复制,是掌握 Redis 高可用方案的必经之路。
在本篇博客中,我们将全方位解析 Redis 的主从复制机制。您将了解到:
- 如何操作:包括如何建立或断开复制连接、如何保证复制过程的安全性以及从节点的只读配置。
- 如何规划:介绍复制所支持的各种拓扑结构(如一主一从、一主多从、树状结构等),以及不同架构的适用场景与优缺点。
- 如何理解:深入剖析复制的工作原理,详细讲解建立复制、全量复制、部分复制、心跳检测等核心过程背后的实现细节。
主从复制配置
参与复制的 Redis 实例划分为主节点 (master)和从节点(slave)。每个从结点只能有⼀个主节点,而⼀个主节点可以同时具有多个从结点。复制的数据流是单向的,只能由主节点到从节点。配置复制的方式有以下三种:
- 在配置文件中加入 slaveof {masterHost} {masterPort} 随 Redis 启动生效。
- 在 redis-server 启动命令时加入 –slaveof {masterHost} {masterPort} 生效。
- 直接使用 redis 命令:slaveof {masterHost} {masterPort} ⽣效。
下面我将通过修改配置文件的方式来实现主从复制的配置。
一般来说,主从复制实现的是分布式系统,但由于小编的财力实在有限,只能在本地模拟一个主从复制的环境。实际生产环境中,虽然说是每个主机都作为单机部署,但实际上往往会通过容器化等方式来实现更灵活的部署,这里暂时不做过多介绍,后面会有相关内容。
我会将 Redis 的配置文件复制多份给从节点使用,并在每个从节点的配置文件中加入对应的 slaveof 配置。
当然,若我们想要使用主从复制,首先应当将配置文件中的 daemonize 选项设置为 yes,以便在后台运行。该选项是用来指定 Redis 服务器是否以守护进程的方式运行的(一般来说这个选项是默认开启的)。
然后我们分别为每个从节点配置对应的 slaveof 选项:
# 从节点1的端口号配置
# Accept connections on the specified port, default is 6379).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6380
# 从节点2的端口号配置
# Accept connections on the specified port, default is 6379).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6381
# 从节点1的主从配置
slaveof 127.0.0.1 6379
# 从节点2的主从配置
slaveof 127.0.0.1 6379
而后,我们就可以启动这两个从节点了。启动命令与主节点类似,只需指定配置文件即可:
# 启动从节点1
redis-server slave1.conf
# 启动从节点2
redis-server slave2.conf
查看主从复制状态
我们通过命令可以查看当前的主从复制状态:
┌─[root@VM-16-15-debian] - [/home/ljx/redistest/project2] - [1044]
└─[$] netstat -ntpa | grep redis-serve [22:39:59]
tcp 0 0 0.0.0.0:6380 0.0.0.0:* LISTEN 1077922/redis-serve
tcp 0 0 0.0.0.0:6381 0.0.0.0:* LISTEN 1077941/redis-serve
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 1042293/redis-serve
tcp 0 0 127.0.0.1:6379 127.0.0.1:59180 ESTABLISHED 1042293/redis-serve
tcp 0 0 127.0.0.1:59180 127.0.0.1:6379 ESTABLISHED 1077941/redis-serve
tcp 0 0 127.0.0.1:59174 127.0.0.1:6379 ESTABLISHED 1077922/redis-serve
tcp 0 0 127.0.0.1:6379 127.0.0.1:59174 ESTABLISHED 1042293/redis-serve
tcp6 0 0 ::1:6380 :::* LISTEN 1077922/redis-serve
tcp6 0 0 ::1:6381 :::* LISTEN 1077941/redis-serve
tcp6 0 0 ::1:6379 :::* LISTEN 1042293/redis-serve
其中每一行的含义如下:
LISTEN状态的端口(如 6379、6380、6381):- 这些是 Redis 主节点和从节点各自监听的服务端口。
- 例如:
0.0.0.0:6379、0.0.0.0:6380、0.0.0.0:6381表示主节点和两个从节点分别监听在本机所有网卡的 6379、6380、6381 端口,等待客户端或其他 Redis 节点的连接。::1:6379、::1:6380、::1:6381是 IPv6 的监听端口,作用同上。
ESTABLISHED状态的端口:- 这些是主从节点之间已经建立的 TCP 连接。
- 例如:
127.0.0.1:6379 <-> 127.0.0.1:59180、127.0.0.1:6379 <-> 127.0.0.1:59174等,表示主节点 6379 端口和从节点的某个本地端口(如 59180、59174)之间已经建立了主从同步的数据通道。
- 这些连接保证了主节点可以实时地将数据同步到从节点。
这里讲解一个小细节,我们的主节点是通过 service 命令来管理的,而不是直接通过 redis-server 启动的。这意味着主节点的进程是由系统服务管理的,可以更方便地进行监控和管理。若我们尝试直接杀掉主节点的进程,系统会自动重启它。是由 1 号进程管理的,验证如下:
┌─[root@VM-16-15-debian] - [/home/ljx/redistest/project1/build] - [1012]
└─[$] ps axj | grep redis [19:46:17]
1 1039968 1039968 1039968 ? -1 Ssl 105 0:00 /usr/bin/redis-server 0.0.0.0:6379
1041179 1041741 1041740 1041065 pts/0 1041740 S+ 0 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv redis
┌─[root@VM-16-15-debian] - [/home/ljx/redistest/project1/build] - [1013]
└─[$] kill -9 1039968 [19:46:19]
┌─[root@VM-16-15-debian] - [/home/ljx/redistest/project1/build] - [1014]
└─[$] ps axj | grep redis [19:48:54]
1 1042293 1042293 1042293 ? -1 Ssl 105 0:00 /usr/bin/redis-server 0.0.0.0:6379
1041179 1042301 1042300 1041065 pts/0 1042300 S+ 0 0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv redis
可以看到,服务是由 1 号进程管理的,我将主节点进程杀掉后,系统会自动重启它(前后的 PID 变化),自然就是 1 号进程重启的该进程
我们分别启动主节点和两个从节点的服务后,可以通过 redis-cli 命令行工具连接到主节点和从节点,利用 info replication 命令查看主从复制状态:
首先是主节点的状态:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=1595,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=1595,lag=0
master_failover_state:no-failover
master_replid:1d0a5e10c42ce1b2af4b14795cbe0c3ff81aef53
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1595
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1595
对这些信息的解释如下:
role:master:表示当前节点是主节点。connected_slaves:2:表示当前主节点有两个从节点连接。slave0和slave1:分别表示两个从节点的状态信息,包括 IP 地址、端口、状态、偏移量和延迟等。master_failover_state:no-failover:表示当前没有故障转移发生。master_replid和master_replid2:表示主节点的复制 ID。master_repl_offset:表示主节点的复制偏移量。second_repl_offset:表示第二个复制偏移量。repl_backlog_active:表示复制积压缓冲区是否激活。repl_backlog_size:表示复制积压缓冲区的大小。repl_backlog_first_byte_offset和repl_backlog_histlen:表示复制积压缓冲区的相关信息。
偏移量是用来标注数据在复制过程中的位置和状态的,帮助我们更好地理解主从复制的机制和数据流动。如果主从节点的偏移量一致,则说明主从节点的数据是一致的。
有关主从复制的更多信息,可以参考 Redis 官方文档中的 Replication 部分。状态中的后面四个选项我将会在后续的内容中详细介绍。
然后是从节点的状态,我们以第一个从节点为例,来看看其配置:
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_read_repl_offset:3275
slave_repl_offset:3275
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:1d0a5e10c42ce1b2af4b14795cbe0c3ff81aef53
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3275
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3275
你会发现,从节点的配置和主节点大多数信息是相同的,其中 role:slave 表示当前节点是从节点,而 master_* 相关的配置项则指向主节点的信息。这些信息帮助从节点与主节点保持同步,并确保数据的一致性。
可以看到的是,即便是从节点,仍然有 connected_slaves:0 这样的配置项,这说明从节点也是可以有从节点的。
这样一来,主节点的数据就会被同步到从节点上,从节点可以通过 info replication 命令查看当前的复制状态。
slaveof
一、SLAVEOF 指令详解
SLAVEOF 是一个功能强大的管理命令,用于动态地配置和管理 Redis 的主从复制关系。它主要有两种用法:
1. 建立复制关系:SLAVEOF master_ip master_port
- 功能:让当前 Redis 服务器成为指定主节点(master)的从节点(slave/replica)。
- 流程:
- 断开旧关系:如果当前服务器已经是另一个主节点的从节点,则会先断开与旧主节点的复制连接。
- 建立新连接:与新的主节点建立网络连接。
- 数据同步:
- 全量复制:从节点会先执行一个全量同步。它会清空自己的所有数据,然后主节点会生成一个当前数据集的 RDB 快照文件并发送给从节点。从节点接收并加载这个 RDB 文件,从而获得主节点在某个时间点的完整数据副本。
- 部分复制:在全量同步之后,主节点会将后续新的写命令持续地、异步地发送给从节点,以保持数据实时一致。
- 应用场景:扩容读性能、做数据备份、为高可用方案(哨兵/集群)做准备。
2. 断开复制关系:SLAVEOF NO ONE
- 功能:让当前从节点停止复制,并晋升为一个独立的主节点。
- 流程:
- 断开连接:停止与主节点的复制连接,不再接收主节点的数据流。
- 晋升为主:自身的角色从
slave变为master。 - 保留数据:关键点:它不会清空自身已有的数据。它只是停止了数据同步,但断开那一刻它所拥有的所有数据都会保留下来。这使得它可以在原主节点宕机时,随时准备接管服务(通常需要配合哨兵来自动完成)。
- 应用场景:故障恢复后的人工干预、网络分区后的重置、将某个从节点提升为新的主节点。
3. 切主操作:SLAVEOF new_master_ip new_master_port
这实际上是第一种用法的延伸。当一个从节点执行此命令指向一个新的主节点时,会发生:
- 断开旧主:等同于先执行了
SLAVEOF NO ONE的第一步(断开连接)。 - 连接新主:与新的主节点建立连接。
- 删除数据:关键区别:这里会清空从节点当前的所有数据! 因为要保证数据完全来自于新的主节点,必须从一个干净的状态开始同步。
- 全量同步:从新主节点执行一次完整的全量复制流程。
二、SLAVEOF 自己连接自己的后果分析
现在,我们尝试让 6381 端口的子节点执行 SLAVEOF 127.0.0.1 6381(即自己连接自己)
发生了什么?(流程拆解)
角色分裂:服务器 X(端口 6381)接收到命令,试图成为自己的从节点。它的逻辑上分裂为两个角色:主节点 X 和 从节点 X。
从节点 X 的流程:
- 它遵循
SLAVEOF的标准流程,首先尝试与“主节点 X”(即自己)建立连接。从网络协议上看,连接自己是成功的。 - 连接成功后,它发送
PING,自己回复自己,所以认为主节点健康。 - 接着,它发起同步请求
PSYNC,将自己的复制 ID 和偏移量发给“主节点 X”,请求增量数据。
- 它遵循
主节点 X 的回应:
- “主节点 X”收到了来自“从节点 X”的
PSYNC请求。 - 然而,“主节点 X” 自身也是一个从节点(因为它执行了
SLAVEOF命令)。根据 Redis 的核心设计原则:一个从节点在未与其主节点完成同步之前,是不能为其他从节点提供数据的。因为它的数据可能不是最新的,直接同步会给其他节点带来脏数据。 - 因此,“主节点 X”会检查自身状态,发现
myself->masterhost是存在的(指向它自己),于是它果断地拒绝了“从节点 X”的同步请求,并回复错误:-NOMASTERLINK Can't SYNC while not connected with my master(“我连我自己的主都还没连上呢,没法给你同步!”)。
- “主节点 X”收到了来自“从节点 X”的
死循环形成:
- “从节点 X”收到拒绝回复后,判定同步失败。
- 同步失败会触发重试机制。在短暂的延迟(约 1 秒)后,“从节点 X”又会发起新一轮的连接和同步请求。
- 这个循环会每秒一次地、永不停歇地进行下去。
会导致什么后果?
- CPU 资源耗尽:每一次重试同步,尤其是如果触发了全量同步的判断,都会导致
fork()操作。频繁的fork()会消耗大量 CPU 资源,导致服务器卡死。 - 内存消耗飙升:
fork()操作在写时复制(Copy-On-Write)机制下,如果实例数据量大,会导致内存占用显著增加,有被系统强制杀死的风险。 - 服务不可用:Redis 进程忙于处理这个无意义的死循环,无法有效处理外部客户端的正常请求,性能急剧下降甚至无响应。
- 日志磁盘爆满:日志文件会被每秒一轮的重复错误信息迅速填满。
如何修复?
修复方法非常简单直接,就是打破这个循环:
# 连接到出问题的 Redis 服务器
redis-cli -p 6381
# 执行命令,让它停止扮演从节点角色,恢复正常
127.0.0.1:6381> SLAVEOF NO ONE
OK
执行后,它解除了自己的从节点身份,循环立即终止,CPU 和内存使用率会逐渐恢复正常。
总结
| 操作 | 正常用法 | 错误用法(自己连自己) |
|---|---|---|
| 命令 | SLAVEOF <真实主节点IP> <端口> |
SLAVEOF 127.0.0.1 <自身端口> |
| 逻辑 | 两个独立进程间的网络通信和数据同步。 | 单个进程内部的逻辑死锁和自循环。 |
| 数据 | 从节点会拥有主节点的数据副本。 | 数据不会有任何变化,但进程忙于循环,无法服务。 |
| 后果 | 实现读写分离、数据冗余。 | 服务不可用,CPU/内存资源被耗尽。 |
| 性质 | 核心的功能特性。 | 危险的错误配置和运维事故。 |
切记:SLAVEOF 是一个强大的工具,但绝对不要指向自己。在自动化脚本和运维操作中,务必加入检查逻辑,防止这种“自杀式”配置的发生。
Redis 的复制拓扑非常灵活,可以根据不同的应用场景选择最合适的结构。这三种结构在复杂性、性能和容灾能力上各有侧重。
拓扑结构
1. 一主一从结构
这是最简单、最基础的复制拓扑。
结构描述:一个主节点(Master)配一个从节点(Slave)。
示意图:
Redis-A (Master) | | Redis-B (Slave)
优点
- 架构简单:部署和维护都非常容易,逻辑清晰。
- 故障转移基础:这是实现高可用的最小单元。当主节点宕机时,可以手动(或通过哨兵自动)将从节点
SLAVEOF NO ONE提升为主节点,继续提供服务,实现故障恢复。 - 读写分离与持久化优化:
- 读写分离:可以让应用程序在主节点上写,在从节点上读,分担负载。
- 持久化优化:可以在从节点上开启 AOF 持久化,而在主节点上关闭。这样做的好处是:
- 避免了持久化(生成 RDB 或写 AOF)对主节点性能造成的任何干扰,保证主节点极高的写入性能。
- 从节点同样可以保证数据的安全性(因为它有完整的 AOF 日志)。这是一种非常经典的性能与安全兼顾的部署方式。
缺点
- 有限的读扩展能力:只有一个从节点来处理读请求,读性能的扩展能力有限。
- 容错性一般:如果唯一的从节点也宕机了,系统就失去了备份和读扩展能力,直到从节点恢复。
- 主节点负载:主节点仍需承担复制所有数据到一个从节点的压力。
适用场景:数据量不大、读请求不是特别多的场景;作为高可用方案的基础单元;希望保证数据安全但又想最大化主节点写入性能的场景。
2. 一主多从结构(星形结构)
这是最常见的拓扑结构,主要用于读写分离。
结构描述:一个主节点(Master)配多个从节点(Slave)。
示意图:
Redis-A (Master) / | \ / | \ Redis-B Redis-C Redis-D ... (Slaves)
优点
- 强大的读扩展能力:这是其核心优势。通过部署多个从节点,可以将大量的读请求负载均衡到各个从节点上,极大地提升了系统的整体读吞吐量,轻松应对高并发读场景。
- 专用从节点:可以为一个从节点分配特殊的任务。例如,专门用于执行
keys、sort等耗时较长的命令,或者专门用于做每日的 RDB 快照备份,而不会影响为线上提供服务的其他从节点。 - 更高的数据冗余:多个从节点意味着多份数据副本,数据安全性更高。
缺点
- 加重主节点负担:这是最大的缺点。多个从节点会导致主节点写命令的多次发送。主节点需要为每一个从节点单独发送一份数据副本。如果从节点数量非常多(例如几十个),主节点的网络和 CPU 资源将大量消耗在复制操作上,反而会影响其处理正常写命令的能力。
- 复制延迟放大:如果主节点写入压力巨大,所有从节点都可能出现不同程度的复制延迟(Replication Lag)。从节点越多,最后一个完成同步的从节点的延迟可能越大。
- 资源消耗:每个从节点都会占用独立的内存来存储完整的数据集,对物理资源的消耗更大。
适用场景:读远大于写的经典互联网业务场景,需要显著提升读性能。
3. 树形(分层)主从结构
这是一种用于优化一主多从缺点的进阶拓扑结构。
结构描述:从节点不仅可以复制主节点,还可以作为其他从节点的主节点,形成一种层级关系。中间的从节点既是下游节点的 Slave,又是上游节点的 Master。
示意图:
Redis-A (Master) / \ / \ Redis-B (Slave/Master) Redis-C (Slave) / \ / \ Redis-D (Slave) Redis-E (Slave)- A 是主节点。
- B 和 C 是 A 的从节点。
- D 和 E 是 B 的从节点(此时 B 是 D 和 E 的主节点)。
优点
- 显著减轻主节点压力:这是其核心价值。有效降低主节点负载和需要传送给从节点的数据量。主节点 A 只需要将数据同步给 B 和 C 两个节点,再由 B 节点将数据同步给 D 和 E。这样,主节点只需要处理 2 个从节点的复制请求,而不是 4 个。
- 网络优化:在跨机房或跨地域部署时,可以让一个机房内的一个从节点(如 B)作为本机房的其他从节点(D、E)的主节点,避免所有机器都直接跨网络连接远方的主节点,节省了带宽并降低了延迟。
缺点
- 架构复杂:部署和维护的复杂度更高。
- 延迟增加:数据需要经过多层传递,到达最下层从节点(如 D、E)的延迟会比直接连接主节点的从节点(如 C)更高。
- 故障点增加:中间层的节点(如 B)变得非常关键。如果它宕机,其下层的所有从节点(D、E)都会中断复制。
适用场景:主节点需要挂载大量从节点(例如超过 10 个)的场景;跨地域部署时优化网络带宽和成本的场景。
总结对比表
| 拓扑结构 | 优点 | 缺点 | 核心应用场景 |
|---|---|---|---|
| 一主一从 | 简单、故障转移基础、可优化持久化 | 读扩展性差 | 数据备份、高可用基础、写密集型场景 |
| 一主多从 | 读性能扩展性极强、数据冗余高、职责分离 | 主节点压力大、资源消耗多 | 读写分离,读多写少的经典业务 |
| 树形主从 | 极大减轻主节点压力、优化网络带宽 | 架构复杂、延迟增加、故障点增加 | 挂载大量从节点、跨地域部署 |
最终的选择取决于具体需求:是更注重读性能、写性能,还是数据的安全性,以及运维能力和基础设施情况。
主从结构服务器重启问题
通过上述方式创建主从节点后,若我们用 service redis restart 重启主节点,会发现主节点重启失败,这是因为当我们利用该指令启动服务是通过 redis 用户启动的服务,而我们通过 redis-cli 连接时使用的是 root 用户。而 redis-cli 指令是在主节点创建之后用来创建从节点的。这就意味着这些从节点创建后也会在相同的目录下创建和 redis 用户一样的 aof 或 rdb 文件。当我们创建主节点时,文件的拥有者和所属组是 redis 用户:
┌─[root@VM-16-15-debian] - [~redis/appendonlydir] - [1145]
└─[$] rm -rf appendonly.aof.* [16:39:32]
┌─[root@VM-16-15-debian] - [~redis/appendonlydir] - [1146]
└─[$] service redis-server restart [16:41:05]
┌─[root@VM-16-15-debian] - [~redis/appendonlydir] - [1147]
└─[$] ll [16:41:10]
total 8.0K
-rw-rw---- 1 redis redis 89 Aug 21 16:41 appendonly.aof.1.base.rdb
-rw-r----- 1 redis redis 0 Aug 21 16:41 appendonly.aof.1.incr.aof
-rw-r----- 1 redis redis 88 Aug 21 16:41 appendonly.aof.manifest
当我们利用 redis-cli 命令创建从节点时,这些从节点的 aof 或 rdb 文件的拥有者和所属组会变成 root 用户。这就导致了权限问题,从而使得主节点在重启时无法正常加载这些文件。
┌─[root@VM-16-15-debian] - [~redis/appendonlydir] - [1148]
└─[$] ll [16:41:26]
total 12K
-rw-rw---- 1 redis redis 89 Aug 21 16:41 appendonly.aof.1.base.rdb
-rw-r----- 1 redis redis 0 Aug 21 16:41 appendonly.aof.1.incr.aof
-rw-r--r-- 1 root root 89 Aug 21 16:41 appendonly.aof.7.base.rdb
-rw-r--r-- 1 root root 0 Aug 21 16:41 appendonly.aof.7.incr.aof
-rw-r--r-- 1 root root 88 Aug 21 16:41 appendonly.aof.manifest
关系如下:

可以看到,因为 Redis7 版本后对混合持久化采用版本号轮转的方式来存储的,因此 base 文件和 incr 文件并不冲突,但是 manifest 文件的拥有者和所属组会变成 root 用户,这就会导致 service 命令重启主节点后主节点发现 appendonly.aof.manifest 文件时权限不足,而且也没有办法对该文件进行删除,故无法在指定的路径下创建新的 manifest 文件,就会导致重启失败。
解决方法很简单,我们让子节点的持久化文件存储在和主节点不同的目录下即可。我们可以在从节点的配置文件中指定不同的 appendonly 和 dbfilename 路径。
# 从节点1
# Note that you must specify a directory here, not a file name.
dir /home/ljx/redistest/project2/slave1
# 从节点2
# Note that you must specify a directory here, not a file name.
dir /home/ljx/redistest/project2/slave2
这样一来,我们就让每个不同的节点的持久化文件存储在不同的目录下,避免了权限冲突的问题。
同时,在生产环境中,建议所有 Redis 实例都统一用 redis 用户启动,避免 root 写入文件
主从复制原理
Redis 主从复制过程概要
Redis 主从复制是数据同步的核心机制,其建立过程主要包含以下六个关键步骤。下图清晰地展示了从节点启动后,与主节点建立连接并完成数据同步的完整流程:
flowchart TD
A[从节点配置 slaveof <master_ip> <master_port>] --> B
subgraph B [第1步: 保存主节点信息]
B1[保存master_ip与master_port\nmaster_link_status:down]
end
B --> C
subgraph C [第2步: 建立TCP连接]
C1[从节点定时任务尝试连接主节点]
C2{连接成功?}
C2 -- 是 --> D[发送PING命令]
C2 -- 否 --> C3[等待重试]
end
D --> E{PONG响应正常?}
E -- 是 --> F
E -- 否 --> C3[断开连接, 等待重试]
subgraph F [第3步: 权限验证]
F1[主节点要求密码?]
F1 -- 是 --> F2[从节点提供masterauth]
F2 --> F3{密码验证通过?}
F3 -- 是 --> G
F3 -- 否 --> H[复制过程停止]
F1 -- 否 --> G
end
subgraph G [第4步: 同步数据集]
G1[首次复制?]
G1 -- 是 --> G2[进行全量同步]
G1 -- 否 --> G3[尝试部分同步]
end
G --> I
subgraph I [第5步: 命令持续复制]
I1[主节点持续发送\n修改命令]
I2[从节点执行命令\n保持数据一致性]
I1 --> I2
end
详细步骤说明
1. 保存主节点信息
- 触发:从节点 (
slave) 配置slaveof <master_ip> <master_port>并启动后。 - 动作:从节点将主节点的 IP (
master_host) 和端口 (master_port) 保存到自身。 - 状态:此时主从连接尚未建立,连接状态 (
master_link_status) 为down。
2. 主从建立 Socket 连接
- 触发:从节点内部有一个每秒运行的定时任务,用于维护复制逻辑。
- 动作:定时任务发现新配置的主节点后,会尝试与主节点 (
master) 建立一个 TCP 网络连接。 - 重试:如果连接失败,定时任务会无限重试,直到连接成功或用户停止复制。
3. 发送 PING 命令(应用层健康检查)
- 触发:TCP 连接成功建立后。
- 目的:检查主节点是否在应用层正常工作,以及当前连接的网络状态是否良好。
- 结果:
- 成功:主节点返回
PONG,进入下一步。 - 失败:从节点会断开 TCP 连接,并等待定时任务下次重新建立连接。
- 成功:主节点返回
4. 权限验证
- 触发:PING-PONG 检查通过后。
- 验证方式:
- 如果主节点配置了
requirepass参数,则需要进行密码验证。 - 从节点需要通过
masterauth参数配置相同的密码。
- 如果主节点配置了
- 结果:
- 成功:复制过程继续。
- 失败:从节点的复制过程停止。
5. 同步数据集
- 触发:权限验证通过后,尤其是首次建立复制的场景。
- 动作:主节点将其持有的全部数据发送给从节点。
- 特性:这是复制过程中最耗时的一步,根据不同情况分为:
- 全量同步:从节点首次连接或无法部分同步时,主节点生成
RDB快照并发送。 - 部分同步:连接中断后重连,主节点仅发送中断期间丢失的命令(依赖复制积压缓冲区
repl-backlog-buffer)。
- 全量同步:从节点首次连接或无法部分同步时,主节点生成
6. 命令持续复制
- 触发:全量/部分同步完成后。
- 动作:
- 主节点每执行一个会修改数据的写命令,都会异步地发送给从节点。
- 从节点接收并执行相同的命令,从而保证主从数据的一致性。
- 状态:复制进入稳定的持续阶段,只要连接保持,数据就会一直同步。
Redis 数据同步核心机制:PSYNC 详解
在 Redis 主从复制中,数据同步是保证数据一致性的基石。PSYNC 命令作为 SYNC 的增强版,有效地解决了全量复制在高开销和网络抖动场景下的痛点,支持全量复制和部分复制两种模式。
一、两种同步模式
全量复制 (Full Resynchronization)
- 场景:主要用于初次建立主从关系的场景。
- 过程:主节点将当前内存中的全部数据生成一个 RDB 快照文件,发送给从节点。从节点清空自身旧数据后,加载 RDB 文件。
- 开销:当数据量巨大时,会对主节点的 CPU、内存、磁盘 I/O 以及网络带宽造成巨大压力。
部分复制 (Partial Resynchronization)
- 场景:用于处理因网络闪断等原因造成数据丢失后重连的场景。
- 过程:从节点重新连接后,主节点仅补发中断期间缺失的命令数据。
- 优势:补发的数据量远小于全量数据,开销小,效率高,是保障复制高效的关键。
二、PSYNC 的核心要素:复制 ID 与偏移量
PSYNC 命令的语法为:PSYNC <replid> <offset>。其运行逻辑依赖于两个核心机制:
复制 ID (Replication ID)
- 每个主节点都有一个唯一的
master_replid,用于标识一个特定的数据集。主节点重启或从节点晋升为主节点时,会生成一个新的replid。 - 从节点会保存主节点的
replid,并在尝试重连时发送给主节点。 master_replid2的妙用:节点(尤其是曾切换角色的节点)会记录两组replid。例如,从节点因网络分区“自立为主”后,会生成新的master_replid,同时将旧主节点的replid保存在master_replid2中。这样网络恢复后,它可以根据master_replid2找回原来的主从关系,避免不必要的全量同步。
- 每个主节点都有一个唯一的
复制偏移量 (Replication Offset)
- 主从节点各自维护一个偏移量 (
offset)。 - 主节点每传播一个写命令,其偏移量
master_repl_offset就会增加该命令的字节长度。 - 从节点每接收到并执行一个命令,其偏移量
slave_repl_offset也会相应增加。 - 作用:通过对比主从节点的偏移量,可以判断数据是否一致。当从节点重连时,通过上报自己的
offset,主节点便能判断需要补发哪些数据。
- 主从节点各自维护一个偏移量 (
三、PSYNC 的工作流程
其工作流程可以概括为以下几个步骤,如下图所示:
flowchart TD
A[从节点发送 PSYNC 请求] --> B{主节点判断}
B -- replid 不匹配
或 offset 不在积压缓冲区 --> C[回复 +FULLRESYNC
触发全量复制]
B -- replid 匹配且
offset 在积压缓冲区内 --> D[回复 +CONTINUE
触发部分复制]
C --> E[从节点接收RDB快照并加载]
D --> F[主节点发送积压缓冲区内
从offset之后的所有命令]
E --> G[进入命令传播阶段
主持续发送写命令]
F --> G
- 从节点发送请求:从节点向主节点发送
PSYNC ? -1(请求全量复制)或PSYNC <replid> <offset>(请求部分复制)。 - 主节点决策:主节点根据收到的
replid和offset判断如何响应:- 如果
replid与自身不匹配,或offset背后的数据不在复制积压缓冲区中,则回复+FULLRESYNC <replid> <offset>,触发全量复制。 - 如果
replid匹配且offset背后的数据仍在缓冲区中,则回复+CONTINUE,触发部分复制,并将缺失的命令流发送给从节点。
- 如果
- 同步执行:从节点根据响应执行全量或部分同步流程。
- 命令持续传播:同步完成后,主节点进入命令传播阶段,持续将新的写命令发送给从节点,维持数据最终一致性。
四、总结与优势
- 自动化:
PSYNC过程由 Redis 自身管理,开发者无需手动干预。 - 高效性:部分复制机制极大地降低了网络闪断带来的复制开销。
- 可靠性:通过
Replication ID和Offset的巧妙设计, robustly 处理了主从切换和重连的各种复杂场景。 - 非阻塞:与旧的
SYNC命令不同,PSYNC执行期间不会阻塞主节点的其他操作。
注意事项
在 Redis 新版本当中,sync 指令也是采用的创建后台子进程的方式来进行全量数据的同步,这样可以避免阻塞主线程,提高系统的整体性能。
if (!strcasecmp(c->argv[0]->ptr,"psync")) {
......
} else {
/* If a slave uses SYNC, we are dealing with an old implementation
* of the replication protocol (like redis-cli --slave). Flag the client
* so that we don't expect to receive REPLCONF ACK feedbacks. */
c->flags |= CLIENT_PRE_PSYNC;
}
if (server.child_type == CHILD_TYPE_RDB &&
server.rdb_child_type == RDB_CHILD_TYPE_DISK){
......
}else if (server.child_type == CHILD_TYPE_RDB &&
server.rdb_child_type == RDB_CHILD_TYPE_SOCKET)
{
......
} else {
if (server.repl_diskless_sync && (c->slave_capa & SLAVE_CAPA_EOF) &&
server.repl_diskless_sync_delay) {
......
} else {
if (!hasActiveChildProcess()) {
startBgsaveForReplication(c->slave_capa, c->slave_req);
}
}
}
可以看到,8.2.1 版本中的 Redis 在实现 SYNC 时,同样会让它执行 startBgsaveForReplication 从而创建子进程实现复制,我们查询 Redis 文档可以看到:

“在较新版本的 Redis 中,它已被 PSYNC 取代”,这是官方文档的原话
Redis 全量复制
全量复制是 Redis 主从复制中最为基础和彻底的数据同步方式。它确保了从节点在初次建立复制或无法进行部分复制时,获得与主节点完全一致的数据副本。然而,这个过程成本高昂,理解其机制对于优化 Redis 架构至关重要。
一、全量复制的核心流程
全量复制的过程可以清晰地分为以下几个阶段,其完整流程如下图所示:
flowchart TD
A[从节点发送PSYNC ? -1] --> B[主节点回复+FULLRESYNC
并携带replid与offset]
B --> C[主节点执行bgsave
生成RDB快照]
C --> D[主节点将RDB文件发送给从节点]
D --> E[从节点将RDB文件保存至本地磁盘]
C -.-> F[主节点将bgsave期间的
写命令存入复制缓冲区]
E --> G[从节点清空自身旧数据]
G --> H[从节点加载RDB文件]
F --> I[主节点发送复制缓冲区中的命令]
I --> J[从节点执行这些命令
追平主节点最新状态]
H --> K[若开启AOF, 从节点执行
BGREWRITEAOF重写AOF文件]
阶段一:连接与协商
- 发起同步:从节点启动后,向主节点发送
PSYNC ? -1命令。参数?和-1表明从节点未知主节点的运行 ID 和复制偏移量,意图进行全量复制。 - 主节点响应:主节点识别出全量复制的请求,回复
+FULLRESYNC <runid> <offset>。其中包含主节点的运行 ID 和当前的复制偏移量,从节点会保存这些信息以备后用。
阶段二:RDB 快照生成与传输 3. 生成快照:主节点调用 bgsave 命令,在后台派生一个子进程生成 RDB 快照文件。此举避免了阻塞主进程。 4. 传输数据:RDB 文件生成后,主节点将其发送给从节点。从节点会将其暂存到本地磁盘。
阶段三:数据加载与追赶 5. 清空旧数据:从节点在加载新的 RDB 数据前,会清空自身所有旧数据,以确保状态的一致性。 6. 加载快照:从节点加载接收到的 RDB 文件到内存中,此时其数据状态与主节点开始执行 bgsave 那个时刻的点完全相同。 7. 补发增量数据:在 bgsave 子进程生成 RDB 期间,主节点接收的新写命令会被写入一个专门的复制缓冲区。待 RDB 传输完成,主节点会将该缓冲区内的所有命令补发给从节点。从节点执行这些命令,从而追平主节点的最新状态。
阶段四:后续处理 8. AOF 重写:如果从节点配置了 AOF 持久化,在加载完 RDB 后,它会自动触发 BGREWRITEAOF 操作,立即生成一份与当前内存数据对应的最新 AOF 文件。
二、全量复制的高成本分析
全量复制是一项资源密集型操作,其高昂的成本体现在多个环节:
主节点开销
- CPU/内存:
bgsave创建子进程进行快照生成,属于 CPU 密集型操作,可能导致主节点 CPU 占用飙升。同时,子进程会拷贝父进程的内存页,可能导致内存占用翻倍。 - 磁盘 I/O:传统模式下,主节点需要将 RDB 文件写入磁盘。
- CPU/内存:
网络开销
- 需要将整个 RDB 文件通过网络传输。对于数据量达数个 GB 的实例,会占用大量带宽,可能影响其他业务的网络性能。
从节点开销
- 数据清空与加载:从节点清空旧数据和加载新 RDB 的过程是阻塞性的,在此期间从节点无法对外提供服务。
- 磁盘 I/O:从节点需要将接收到的 RDB 数据写入本地磁盘,然后再次从磁盘读取以加载到内存。
结论:应极力避免对数据量大的生产环境实例进行全量复制。优化策略包括:配置合理的 repl-backlog-size 以支持部分复制、保证主从网络稳定、在从节点重启前做好持久化等。
三、优化:无盘复制 (Diskless Replication)
为了 mitigating 磁盘 I/O 带来的开销,Redis 自 2.8.18 版本起支持无盘复制。
- 传统模式:主节点
bgsave→ 写入磁盘 → 从节点读取 RDB 文件。 - 无盘模式:主节点
bgsave→ 直接通过网络发送 RDB 数据给从节点。
优势:省去了主节点磁盘写入和从节点磁盘读取的步骤,显著降低磁盘 I/O 压力,尤其适用于磁盘性能较差的机器或高速网络环境。可通过配置 repl-diskless-sync yes 开启。
在大多数情况下,网络传输速度都是最大的性能瓶颈,因此,即便有了无盘复制的优化,实际上对效率的提升并不大
Redis 部分复制
部分复制是 Redis 为优化全量复制的高昂开销而设计的核心机制。它能够在主从连接出现短暂中断后,仅同步缺失的数据,从而极大地提升复制效率和系统可用性。
一、核心概念:复制积压缓冲区
要理解部分复制,首先要了解其基石——复制积压缓冲区。
是什么:复制积压缓冲区是主节点上一个固定长度的先进先出队列。默认大小为 1MB,在主节点拥有从节点时自动创建。
工作原理:主节点在处理每一个写命令时,除了将其发送给所有从节点,还会写入这个缓冲区。这使得缓冲区中总是保存着最近传播的写命令。
环形结构:缓冲区是一个环形队列,当新数据写满后,会覆盖最旧的数据。其状态可以通过
INFO replication命令监控:repl_backlog_active:1 # 激活状态 repl_backlog_size:1048576 # 缓冲区总大小 (1MB) repl_backlog_first_byte_offset:7479 # 缓冲区起始偏移量 repl_backlog_histlen:1048576 # 当前缓冲区中有效数据的长度核心作用:根据偏移量 (
offset),主节点可以判断从节点缺失的数据是否还在这个缓冲区中。如果存在,即可进行部分复制;如果已被覆盖,则只能退回到全量复制。
二、部分复制的触发与流程
部分复制的核心思想是“缺啥补啥”,其完整流程如下图所示,通常在网络中断后重连时触发:
flowchart TD
A[主从连接因网络中断超时断开] --> B[中断期间: 主节点继续处理写命令
并将其存入复制积压缓冲区]
B --> C[网络恢复后, 从节点重连主节点]
C --> D[从节点发送PSYNC请求
携带之前的主节点replid与自身offset]
D --> E{主节点校验
从节点offset是否在
积压缓冲区范围内?}
E -- 是 --> F
subgraph F [部分复制流程]
F1[主节点回复 +CONTINUE]
F2[从节点接收响应, 准备接收数据]
F3[主节点从积压缓冲区中
查找并发送从offset之后的所有命令]
F4[从节点执行命令, 追平数据]
end
E -- 否 --> G
subgraph G [全量复制流程]
G1[主节点回复 +FULLRESYNC]
G2[触发完整的全量复制过程]
end
F --> H[同步完成, 进入命令持续传播阶段]
G --> H
流程阶段解析:
连接中断与数据缓存:
- 主从节点之间因网络抖动或中断,连接时间超过
repl-timeout配置值,连接断开。 - 中断期间,主节点依然正常处理写命令。这些无法即时发送给从节点的命令被暂存到复制积压缓冲区中。
- 主从节点之间因网络抖动或中断,连接时间超过
重连与同步请求:
- 网络恢复后,从节点自动重连主节点,并重新发起复制请求。
- 从节点使用之前保存的主节点运行 ID 和自身的复制偏移量作为参数,发送
PSYNC <replid> <offset>命令。
主节点验证与决策:
- 主节点验证从节点传来的
replid是否与自身匹配。 - 关键检查:主节点判断从节点传来的
offset是否还在自己的复制积压缓冲区范围内。- 如果
offset仍在缓冲区中:主节点回复+CONTINUE,表示可以进行部分复制。 - 如果
offset已被覆盖:主节点回复+FULLRESYNC,并触发全量复制。
- 如果
- 主节点验证从节点传来的
数据补发与最终一致:
- 主节点从积压缓冲区中提取从节点
offset之后的所有命令,发送给从节点。 - 从节点接收并执行这些命令,最终将自己的数据状态追平至与主节点一致。
- 主节点从积压缓冲区中提取从节点
三、部分复制的优势与配置建议
巨大优势:部分复制机制将中断恢复的代价从传输整个数据集降低为仅传输中断期间的少量写命令,极大地减少了对 CPU、网络和 I/O 资源的消耗,降低了系统延迟。
关键配置:
repl-backlog-size:调整此配置至关重要。对于写操作频繁的系统,1MB 的默认值可能很快被覆盖,导致部分复制失效。建议根据master_repl_offset的增长速度(如每秒偏移量增量)和可能的网络中断时间(如 60 秒)来估算并调大该值(例如设置为100MB),以降低全量复制的概率。repl-timeout:设置合理的超时时间,避免在网络波动时过早断开连接。
四、总结
部分复制是 Redis 主从复制高可用性的关键保障。它通过复制积压缓冲区这一精巧的设计,实现了高效、快速的数据故障恢复。理解和合理配置 repl-backlog-size 等参数,是保证 Redis 主从架构在大流量、不稳定网络环境下依然稳定可靠的关键。
Redis 实时复制:维持数据最终一致性的心跳
当全量或部分复制完成后,主从节点之间的数据状态达到一致。此后,为了保持这种一致性,系统会进入实时复制阶段(也称为命令传播阶段)。这是主从复制过程中最持久和常态化的阶段,确保从节点能够实时跟上主节点的数据变化。
一、核心机制:命令传播
实时复制的核心过程非常简单高效:
- 主节点接收并执行任何一个会修改数据集(如
SET,LPUSH,SADD,DEL等)的写命令。 - 主节点将该命令异步地通过之前建立的 TCP 长连接发送给所有从节点。
- 从节点接收、解析并执行相同的命令,从而使得自身数据状态与主节点保持同步。
这个过程是异步的,意味着主节点不会等待从节点执行完毕后再返回结果给客户端,这保证了高性能,但也带来了极短的主从延迟。
二、生命线:心跳维护机制
为了确保命令传播的 TCP 长连接稳定可用,并能及时发现失败的从节点,Redis 在主从节点之间实现了一套应用层的心跳检测机制。
这套机制的核心作用是保活连接和监控状态,其工作原理如下图所示:
sequenceDiagram
participant M as 主节点
participant S as 从节点
Note over M, S: 心跳检测机制
loop Every 10 Seconds
M->>S: PING
S-->>M: PONG (响应)
end
loop Every 1 Second
S->>M: REPLCONF ACK
end
Note over M: 监控超时
Note over M: 若超过repl-timeout(默认60s)
未收到从节点REPLCONF ACK,
则判定从节点下线,断开连接。
1. 主节点 → 从节点:PING(健康检查)
- 频率:主节点默认每隔 10 秒向从节点发送一次
PING命令。 - 目的:
- 检查从节点是否仍然存活。
- 检查网络连接是否正常。
- 维持长连接活性,防止被中间网络设备因超时断开。
2. 从节点 → 主节点:REPLCONF ACK(偏移量上报)
- 频率:从节点默认每隔 1 秒向主节点发送一次
REPLCONF ACK <offset>命令。 - 目的:
- 上报自身复制偏移量:主节点通过此命令知晓每个从节点的复制进度,可用于监控延迟和判断部分复制的可能性。
- 作为另一种心跳信号:这是对主节点
PING的补充,更频繁地向主节点证明自己的活跃状态。 - 实现读写分离场景下的弱一致性保证(通过
min-slaves-to-write和min-slaves-max-lag配置)。
三、超时判定与故障处理
repl-timeout:这是一个关键配置项,默认值为 60 秒。它定义了主从节点之间心跳响应的超时时间。- 故障判定:如果主节点在
repl-timeout时间内,既没有收到从节点的REPLCONF ACK响应,也没有收到其对PING的PONG回复,主节点就会判定该从节点已下线。 - 处理方式:主节点会主动断开与该从节点的复制连接,以释放资源。从节点恢复后,需要重新发起连接并根据偏移量尝试进行部分复制或全量复制。
四、总结与意义
- 实时复制是主从复制中持续进行的阶段,通过异步传播命令来保持数据一致性。
- 心跳机制是维持复制链路可靠的生命线,它包括主节点的
PING和从节点的REPLCONF ACK。 repl-timeout是判断连接健康度的关键阈值,超时会导致连接断开。- 这种设计使得 Redis 主从复制在保证高性能的同时,也具备了一定的自愈能力,能够自动处理网络波动和从节点临时故障的场景,是构建可靠 Redis 架构的基础。