Redis高可用
一主多从,一个主节点,多个从节点。支持主从同步、从从同步。从从同步主要是为了减轻主节点的同步负担。从节点可以有从节点。
增量同步:Redis同步的是命令流。
快照同步:主节点执行一次bgsave,生成rdb文件,从节点加载rdb文件,加载前需要清空当前内存的数据。
增加从节点:当从节点刚加入集群时,先进行快照同步,同步完后再继续进行增量同步。
无盘复制:主节点直接通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程,主节点会一边遍历内存,一边将序列化的内容发送到从节点,从节点将接收的内容存储到磁盘文件中,再进行一次性加载。
redis高可用的核心目的是保障服务的可用性,保障缓存数据的可用(避免缓存被清空的情况),重建缓存是需要时间的,会对业务造成影响。在主节点挂掉,从节点被提升为主节点时,允许丢失少量的缓存数据,少量的缓存数据的丢失不会对业务有较大的影响。为什么不需要进行同步复制,因为redis存在的巨大意义是因为它快,性能高,如果因为同步而导致性能有较大的损失,则失去了redis最大的优势以及其核心业务价值。
Redis复制
redis复制分为全量复制和增量复制。
全量复制过程:主节点启动后台保存进程以生成RDB文件。同时开始缓冲所有接收到的新写入命令。当后台保存完成时,主节点将数据库文件传输到从节点,从节点将其保存到磁盘,然后加载到内存中。主节点然后将所有缓冲的命令发送到从节点。这以命令流的形式完成,格式与Redis协议本身相同。

主节点日志
9412:M 20 Oct 2025 17:06:26.195 - Accepted 127.0.0.1:42354
9412:M 20 Oct 2025 17:06:26.199 * Replica 127.0.0.1:6380 asks for synchronization
9412:M 20 Oct 2025 17:06:26.199 * Full resync requested by replica 127.0.0.1:6380 # 请求全量同步
# 创建复制积压缓冲区
9412:M 20 Oct 2025 17:06:26.199 * Replication backlog created, my new replication IDs are '756cf5352890fd5a090c7809e968c5198905aed9' and '0000000000000000000000000000000000000000'
# 创建全量复制用RDB文件,
9412:M 20 Oct 2025 17:06:26.199 * Starting BGSAVE for SYNC with target: disk
9412:M 20 Oct 2025 17:06:26.200 * Background saving started by pid 9498
9498:C 20 Oct 2025 17:06:26.202 - RDB: 0 MB of memory used by copy-on-write
9498:C 20 Oct 2025 17:06:26.207 * DB saved on disk
9498:C 20 Oct 2025 17:06:26.208 * RDB: 0 MB of memory used by copy-on-write
9412:M 20 Oct 2025 17:06:26.306 * Background saving terminated with success
9412:M 20 Oct 2025 17:06:26.307 * Synchronization with replica 127.0.0.1:6380 succeeded
从节点日志:
# 建立与主节点的连接
9493:S 20 Oct 2025 17:06:26.194 * Connecting to MASTER 127.0.0.1:6379
9493:S 20 Oct 2025 17:06:26.195 * MASTER <-> REPLICA sync started
9493:S 20 Oct 2025 17:06:26.195 * Non blocking connect for SYNC fired the event.
9493:S 20 Oct 2025 17:06:26.196 * Master replied to PING, replication can continue...
9493:S 20 Oct 2025 17:06:26.198 * Partial resynchronization not possible (no cached master)
# 请求全量同步
9493:S 20 Oct 2025 17:06:26.201 * Full resync from master: 756cf5352890fd5a090c7809e968c5198905aed9:0
9493:S 20 Oct 2025 17:06:26.307 * MASTER <-> REPLICA sync: receiving 7842 bytes from master to disk
9493:S 20 Oct 2025 17:06:26.307 * MASTER <-> REPLICA sync: Flushing old data
9493:S 20 Oct 2025 17:06:26.308 * MASTER <-> REPLICA sync: Loading DB in memory
9493:S 20 Oct 2025 17:06:26.313 * Loading RDB produced by version 6.2.18
9493:S 20 Oct 2025 17:06:26.313 * RDB age 0 seconds
9493:S 20 Oct 2025 17:06:26.313 * RDB memory usage when created 2.31 Mb
9493:S 20 Oct 2025 17:06:26.313 # Done loading RDB, keys loaded: 19, keys expired: 0.
9493:S 20 Oct 2025 17:06:26.313 * MASTER <-> REPLICA sync: Finished with success
主从复制
配置主从复制的三种方式:
- 方式一:配置主从的命令
replicaof <masterip> <masterport>,可以在从节点redis.conf配置文件中进行配置 - 方式二:使用redis客户端(redis-cli)在从节点通过命令
replicaof <masterip> <masterport>进行配置。 - 方式三: 从节点启动时指定主节点
redis-server redis.conf replicaof <masterip> <masterport>。
使用命令行传递的参数格式与
redis.conf文件中使用的格式完全相同,不同之处在于关键字前缀为--。
示例:
127.0.0.1:6381> replicaof 127.0.0.1 6380 # 配置从节点
OK
127.0.0.1:6381> info replication # 查看从节点信息
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
# ...
关闭复制:从节点可执行replicaof no one关闭复制,该节点将从从节点转为主节点,不会丢弃已复制的数据。。
replicaof命令使用时需要注意,如果当前节点已经是从节点,执行replicaof <masterip> <masterport>将停止与旧主节点的复制,并开始与指定新主节点的复制,同时会丢弃旧数据集。
主从复制工作机制:
- 当主节点和从节点连接良好时,主节点通过向从节点发送命令流来同步从节点。
- 当主节点和从节点连接断开时,从节点会重新连接并尝试进行部分同步,它将尝试仅获取断开连接期间遗漏的命令流部分。
- 当无法进行部分同步时,从节点将请求全量同步。
redis默认使用异步复制,redis从节点周期性地向主节点异步确认它们接收到的数据量。客户端可以使用WAIT命令请求特定数据的同步复制。
WAIT命令
WAIT numreplicas timeout 此命令会阻塞当前客户端,直到之前的所有写命令成功传输并至少numereplicas个从节点成功接收到。如果达到了timeout参数指定的值(ms),即使未达到指定数量的副本,命令也不会返回。超时设置为0意味着永久阻塞。
WAIT并不能使redis成为一个强一致性存储。主要提升了在Sentinel或Redis Cluster故障转移场景下的数据安全性。使用WAIT命令后,可以确保在当前连接中,WAIT命令之前的所有写操作,都已经被其返回数量所代表的从节点功能接收。
配置详解
replicaof <masterip> <masterport> replicaof指令让一个redis实例成为另一个redis实例的从节点。
-
- redis复制是异步的,但可以配置主节点在与指定数量的从节点断开连接时停止接受写入。
-
- 如果复制连接中断时间较短,从节点可以执行部分重同步。应根据需求配置复制积压缓冲区大小。
-
- 复制过程自动进行,网络分区后从节点会自动重连主节点并重新同步
masterauth <password> 主节点有密码保护时需要配置,需要在开始复制前进行认证
masteruser <username> 如果默认用户不具备PSYNC等复制所需权限,需要配置专用用户。如果不指定默认使用default用户。当从节点与主节点连接中断或正在同步时,有两种表现模式:
- 当replica-serve-stale-data被设置为yes(默认值)时,继续响应客户端请求,可能返回过期数据,如果是首次同步,数据集可能为空。
- 当replica-serve-stale-data被设置为no时,对除指定命令外(INFO, REPLICAOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, HOST and LATENCY)的所有请求返回”SYNC with master in progress”错误。
repl-diskless-sync no 全量同步策略,disk(磁盘) or socket。
- 磁盘模式:主节点创建子进程生成RDB文件,随后主节点将RDB文件发送给从节点。
- 无盘模式:主节点直接通过socket发送RDB到从节点,不落盘。无盘复制可配置等待时间以支持从多节点并行传输。
repl-diskless-sync-delay 5 无盘复制延迟时间,为了让多个从节点几乎同时请求全量同步时,能够批量同步,减少重复fork和RDB生成的次数,共享同一个RDB快照传输过程,从而提高效率。工作流程:第一个从节点请求全量同步,主节点不立即开始,而是等待repl-diskless-sync-delay秒,在这段延迟期间,其他从节点也可以连接上来并请求同步。延迟结束后,主节点生成一个RDB快照,然后通过网络同时发送给所有在这段延迟期间内排队等待的从节点。如果设置为0,则无盘同步将立即开始。建议在故障恢复后等场景可以配置较高的值(例如5~10秒),而其他场景(动态扩容等)可以适当减少等待时间。
repl-diskless-load disabled 从节点加载RDB的方式(无盘加载为实验性功能,谨慎使用,无盘加载可能会导致failover时数据丢失)。
- disabled: 先存盘再加载
- on-empty-db:仅在安全时无盘加载
- swapdb: 内存保留当前数据副本。 无盘加载:从节点可直接从socket中加载RDB,而无序将RDB落盘后再加载。
repl-ping-replica-period 10 从节点ping主节点间隔,默认值10秒,用于确保连接是活的。
repl-timeout 60 定义了以下三种场景的超时阈值:
- 从节点视角,全量同步SYNC的批量传输超时:从节点执行全量同步时,主节点生成RDB文件并通过网络传输给从节点,若传输时间超过repl-timeout,则从节点会判定同步失败,从节点放弃当前同步,重新尝试全量同步。
- 从节点视角,主节点数据和心跳检测超时:从节点在正常复制期间,若超过repl-timeout未收到主节点的数据或心跳,则认为主节点不可用。从节点断开与主节点的连接,并尝试重新建立连接。
- 主节点视角:从节点心跳响应超时。主节点通过REPLCONF ACK定期检查从节点存活状态。若从节点超过repl-timeout未响应,主节点会判定从节点不可用,主节点断开与从节点的连接。
repl-timeout必须大于repl-ping-replica-period的值。在实际场景中,该值不宜过小,因为网络是最不稳定的。
repl-disable-tcp-nodelay no 禁用从节点socket的TCP_NODELAY(默认no,低延迟优先;yes可减少带宽但增加40ms延迟)
复制积压缓冲区配置(影响部分重同步能力),当从节点断开时,主节点会将复制数据保存在backlog缓冲区中,当从节点重新连接时,不必重新复制全量数据,仅复制丢失的部分复制数据即可。
repl-backlog-size 1mb # 缓冲区大小(至少一个从节点连接时分配)
// 创建复制积压缓冲区,并不需要持久化存储,仅在内存中,影响部分重同步能力,即使丢失,仍可通过全量同步恢复
void createReplicationBacklog(void) {
serverAssert(server.repl_backlog == NULL); // 确保当前没有复制积压缓冲区存在
server.repl_backlog = zmalloc(server.repl_backlog_size);
server.repl_backlog_histlen = 0;
server.repl_backlog_idx = 0;
/* We don't have any data inside our buffer, but virtually the first
* byte we have is the next byte that will be generated for the
* replication stream. */
server.repl_backlog_off = server.master_repl_offset+1;
}
repl-backlog-ttl 3600 # 所有从节点断开后保留缓冲区的时长(秒),0表示永不释放backlog。
从节点优先级(Sentinel选主依据,0表示不能升主,默认100)
replica-priority 100
replica-announced yes 控制该节点是否被Sentinel发现并纳入监控范围。设为no时,该节点不会出现在sentinel replicas <master>命令的结果中。Sentinel不会感知到这个节点的存在。即使设置为no,仍能参与故障转移,有可能被提升为主节点。如果需要彻底禁止某个副本成为主节点,必须同时将其replica-priority设置为0。可用于隐藏某些用于备份、测试或跨地域延迟较高的从节点,使其不参与常规的服务发现,但仍保持数据同步。
min-replicas-to-write 3 要求的最小在线副本数量。当连接的有效从节点数量少于N个,且这些副本的延迟时间都小于或等于M秒时,主节点可以停止接受写请求。这N个从节点必须处于“在线”状态。延迟秒数的计算基于从节点收到的最后一次ping,该延迟必须小于或等于指定值。该选项并不能保证写操作被N个从节点接收,但在可用从节点不足时,可以将数据丢失的风险窗口限制在指定的秒数范围内。例如要求至少3个延迟在10秒内的从节点。当设置为0时关闭此功能,此时主节点没有从节点也可以正常写入。
min-replicas-max-lag 10 允许的最大复制延迟(秒)。
replica-announce-ip 5.5.5.5 | replica-announce-port 1234 在复杂网络环境下(如NAT、Docker、云平台)确保主从复制正确识别的关键配置。自动检测在NAT/端口转发环境下,从节点自动检测的本地IP和端口可能时内部网络地址,对主节点或其他外部服务不可达。对此可以用手动声明机制。replica-announce-ip:从节点向主节点声明自己的外部可访问IP地址;replica-announce-port:从节点向主节点声明自己的外部可访问端口。这两个参数在从节点进行配置。
redis复制原理
每个Redis主实例都有一个ID:它是一个大的伪随机字符串,标记给定数据集的历史记录。每个主实例还有一个偏移量,每生成一个字节的复制流(用于发送给从节点,以修改数据集的新更改更新从节点的状态)就递增。即使没有实际连接的从节点,复制偏移量也会递增,因此基本上每对给定的Replication ID, offset(运行ID和偏移量)识别主实例数据集的精确版本。
void changeReplicationId(void) {
getRandomHexChars(server.replid,CONFIG_RUN_ID_SIZE);
server.replid[CONFIG_RUN_ID_SIZE] = '\0';
}
当从节点连接到主节点时,使用PSYNC命令发送它们旧主节点运行ID和到目前为止已处理的偏移量。这样主节点就可以发送所需的增量部分。但是如果主实例缓冲区中没有足够的积压,或者从节点引用的历史纪录(运行ID)不再已知,就会发生全量同步,这种情况下,从节点将从头开始获取数据集的完整数据。
运行ID基本上标记了数据的给定历史记录。每次一个实例作为主节点从头开始重启时,或一个从节点被提升为主节点时,都会为此节点生成了一个新的运行ID。 连接到主节点的从节点在握手后将继承其运行ID。因此,具有相同的ID的两个实例通过它们持有相同数据但可能在不同时间的事实相关联。正是偏移量起着逻辑时间的作用,用于理解在给定历史记录(运行ID)下,谁持有最新的数据集。
例如,如果两个节点A和B具有相同的运行ID,但一个的偏移量为1000,另一个为1023,这意味着第一个节点缺少应用于数据集的某些命令。这也意味着A通过应用少量命令即可达到与B完全相同的状态。
redis实例有两个运行ID的原因是因为从节点被提升为主节点。故障转移后,被提升的副本需要仍然记住它过去的运行ID,因为该运行ID是旧主实例的ID。这样,当其他从节点与新主节点同步时,它们将尝试使用旧主节点运行ID进行部分同步。这将按预期工作,因为当从节点被提升为主节点时,它将其辅助ID设置为旧主节点的运行ID,记住此ID切换发生时的偏移量。之后它将选择一个新的随机运行ID,因为新的历史纪录开始了。处理新连接的从节点时,主节点将使用当前ID和辅助ID来匹配它们的ID和偏移量。简而言之,这意味这故障转移后,连接到新提升的主实例的副本无需执行完整同步。
void shiftReplicationId(void) {
memcpy(server.replid2,server.replid,sizeof(server.replid));
/* We set the second replid offset to the master offset + 1, since
* the slave will ask for the first byte it has not yet received, so
* we need to add one to the offset: for example if, as a slave, we are
* sure we have the same history as the master for 50 bytes, after we
* are turned into a master, we can accept a PSYNC request with offset
* 51, since the slave asking has the same history up to the 50th
* byte, and is asking for the new bytes starting at offset 51. */
server.second_replid_offset = server.master_repl_offset+1;
changeReplicationId();
serverLog(LL_WARNING,"Setting secondary replication ID to %s, valid up to offset: %lld. New replication ID is %s", server.replid2, server.second_replid_offset, server.replid);
}
为什么被提升为主节点的从节点需要在故障转移后更改其运行ID?存在一种可能,旧主实例由于网络分区仍在作为主节点工作,保留相同的运行ID将违反“任意两个随机实例的相同ID和相同偏移量意味着它们具有相同数据集”这一事实。
一主一从节点的状态:
127.0.0.1:6379> info replication
# Replication
role:master # 主节点
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=14,lag=0
master_failover_state:no-failover
master_replid:f0c92deb38de697d6647b3457773b811b6196573 # 运行ID
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14 # 偏移量
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576 # 复制积压缓冲区大小
repl_backlog_first_byte_offset:1 # 复制积压缓冲区起始偏移量
repl_backlog_histlen:14 # 复制积压缓冲区中数据的长度
127.0.0.1:6380> info replication
# Replication
role:slave # 从节点
master_host:127.0.0.1
master_port:6379
master_link_status:up # 主节点连接状态,up表示连接正常
master_last_io_seconds_ago:2
master_sync_in_progress:0 # 是否正在同步
slave_read_repl_offset:56 # 从节点读取复制偏移量
slave_repl_offset:56 # 从节点复制偏移量
slave_priority:100 # 故障转移优先级
slave_read_only:1
replica_announced:1 # 副本被Sentinel宣告
connected_slaves:0
master_failover_state:no-failover
master_replid:f0c92deb38de697d6647b3457773b811b6196573 # 从节点的运行ID和主节点相同
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:56
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:56
关闭主节点,将从节点提升为主节点
127.0.0.1:6380> replicaof no one # 将从节点提升为主节点
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:84a5cb4c551897e90f38cef233939ede49950ff7 # 新的主节点的运行ID
master_replid2:f0c92deb38de697d6647b3457773b811b6196573 # 旧的主节点的运行ID
master_repl_offset:1568
second_repl_offset:1569
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1568
redis复制如何处理过期键
对于过期键,引起依赖时钟,而节点之间的时钟可能存在偏差,因此Redis复制处理过期键的方式是:不依赖主从实例具有同步时钟的能力(比如NTP),因为时钟同步总有精度上限,永远无法做到完全同步。
- 从节点不使用过期键,而是等待主节点使用过期键。当主节点使用过期键时(或因LRU驱逐),它会合成一个
DEL命令,并将其传输到所有从节点。 - 由于主节点驱动的过期,有时从节点内存中可能仍然存在逻辑上已经过期的键,因为主节点未能及时同步
DEL命令。为了解决这个问题,从节点使用逻辑时钟,仅在不违反数据集一致性的读取操作中报告某个键不存在。通过这种方式,从节点避免报告逻辑上已过期但仍然存在的键。 - 在Lua脚本执行期间,不会执行键过期。从概念上讲,当Lua脚本运行时,主节点的时间是冻结的,因此在脚本运行期间,给定键会一直存在或不存在。这可以防止键在脚本执行过程中过期,并且是必须的,以便保证在数据集上产生相同效果的方式将同一脚本发送到从节点。
一旦从节点被提升为主节点,它将开始独立地使键过期,并且不再需要其旧主节点的任何帮助。
默认情况下,从节点会忽略maxmemory,驱逐键将由主节点处理。并在主节点驱逐键时向从节点发送DEL命令。
参考文档: Redis replication Redis 复制 Redis复制