Logo
活死人の行知路

Redis 主从复制


📅 | 📝 1052 字
#redis

卡片索引

卡片类型说明
为什么需要主从复制概念卡为什么要做主从复制?
主从复制是什么概念卡主从复制是什么?核心概念一览
全量复制 vs 部分复制概念卡全量复制 vs 部分复制(PSYNC)
5 分钟搭建一主一从操作卡5 分钟搭建一主一从
核心配置参数参考卡主从复制核心配置参数速查
常见问题与排查故障卡常见问题与排查
用"图书馆"理解主从复制类比卡费曼技巧:用"图书馆"理解主从复制

概念卡:为什么要做主从复制?

一句话:单机 Redis 有三座大山——故障、容量、QPS。主从复制是翻越这三座山的第一步。

单机 Redis 的三大问题

问题表现本质
单点故障机器挂了,所有客户端连不上 Redis高可用问题
容量瓶颈16G 内存的机器放不下 64G 数据分布式存储问题
QPS 瓶颈单机扛不住 100W QPS(号称 10W)分布式读扩展问题

关键洞察:容量和 QPS 瓶颈是分布式要解决的问题,而故障是高可用要解决的问题。主从复制同时回应了这三个问题。

主从复制能做什么

  • 数据备份:一份数据,多个副本,不怕单点挂掉
  • 读写分离:写走 Master,读走 Replica,扩展读性能
  • 故障转移基础:主从是哨兵(Sentinel)和 Cluster 的前提

核心规则

  • 一个 Master 可以有多个 Replica
  • 一个 Replica 只能有一个 Master
  • 数据流向是单向的:Master → Replica

相关卡片


概念卡:主从复制是什么?

一句话:Master 把数据"复印"给 Replica,Replica 是只读的副本。

拓扑结构

                    ┌─────────────┐
                    │   Master    │  ← 读写
                    │  (6379)     │
                    └──────┬──────┘
                           │ 数据单向流动
               ┌───────────┼───────────┐
               ▼           ▼           ▼
        ┌──────────┐ ┌──────────┐ ┌──────────┐
        │ Replica 1│ │ Replica 2│ │ Replica 3│  ← 只读
        │ (6380)   │ │ (6381)   │ │ (6382)   │
        └──────────┘ └──────────┘ └──────────┘

两种同步方式

方式触发条件特点
全量复制 (Full Resync)首次连接、Master 重启(run_id 变化)、断连超时超出 repl_backlog传整个 RDB,开销大
部分复制 (Partial Resync)短暂断连且在 repl_backlog 范围内只传断连期间的命令,开销小

决定用哪种的关键:PSYNC 命令(见 #概念卡:全量复制 vs 部分复制)

核心概念速查

术语含义
run_id每个 Redis 实例启动时随机生成的唯一标识,重启后变化
master_repl_offset主节点已写入的总字节数,代表"写到哪了"
slave_repl_offset从节点已同步的总字节数
repl_backlog主节点维护的一个环形缓冲区,缓存最近写入的命令,用于部分复制
master_link_status主从连接状态,up = 正常,down = 异常

相关卡片


概念卡:全量复制 vs 部分复制(PSYNC)

一句话:Redis 用 PSYNC 命令决定是"全盘托出"还是"只补遗漏"。

PSYNC 命令的两个参数

PSYNC <run_id> <offset>
参数含义首次同步时传什么?
run_id我知道的 Master 的 ID?(不知道)
offset我知道的 Master 的偏移量-1(不知道)

全量复制流程(Full Resync)

Replica                          Master
  │                                │
  │────── PSYNC ? -1 ────────────►│   // "我不认识你,从头来吧"
  │                                │
  │                                │←── BGSAVE(生成 RDB 快照)
  │                                │←── 同时记录后续写入命令到 repl_backlog
  │◄───── 传输 RDB 文件 ──────────│
  │                                │
  │◄──── 传输 backlog 中的命令 ───│  // RDB 生成后到传输期间的新命令
  │                                │
  │── 清空旧数据 + 加载 RDB ──────►│  // 在内存中重建数据
  │                                │
  │══════ 同步完成 ═══════════════│

关键点:

  1. Master 执行 BGSAVE 生成 RDB 快照
  2. 在 RDB 生成到传输完成期间,Master 把所有新写入的命令存进 repl_backlog
  3. RDB 传完后,再把 backlog 中的命令补传给 Replica
  4. 这样就保证了"全量 + 增量"的无缝衔接

部分复制流程(Partial Resync)

Replica                          Master
  │                                │
  │◄──── 短暂断连(< repl_backlog)─│
  │                                │
  │────── PSYNC <run_id> <offset>─►│  // "我记得你,只补这段"
  │                                │
  │◄──── 只传 backlog 中的命令 ───│  // 不需要传 RDB
  │                                │
  │══════ 同步完成 ═══════════════│

前提条件:

  • Replica 记住了 Master 的 run_id
  • 断连时间不超过 repl_backlog_size 的容量
  • Master 没有重启(run_id 没变)

什么时候触发全量?什么时候触发部分?

场景类型原因
首次连接全量Replica 不知道 Master 的 run_id
Master 重启全量run_id 变了,Replica 认为换了个 Master
断连时间短部分repl_backlog 里还有数据
断连时间长全量repl_backlog 已过期被覆盖
网络抖动恢复部分通常在 backlog 范围内

全量复制的开销

做一次全量复制,Master 和 Replica 都要付出代价:

  • Master 侧:BGSAVE fork 子进程的 CPU 开销 + CoW 内存开销
  • 网络:RDB 文件传输时间(数据量大时可能几分钟)
  • Replica 侧:清空旧数据 + 加载 RDB 到内存
  • 可能触发:AOF 重写(如果 repl-backlog-sync 期间触发了 auto-aof-rewrite)

经验法则:全量复制适合初次同步或小数据量场景。大数据量时考虑增量复制或优化 RDB 传输方式(diskless sync)。

相关卡片


操作卡:5 分钟搭建一主一从

一句话:改两个配置文件,启动两个进程,完事。

前置条件

假设你已经完成了 Redis 安装,源码在 /opt/redis-stable,二进制在 /usr/local/redis/bin/。

如果没有,先看 → #参考卡:主从复制核心配置参数 配置速查

Step 1:创建目录结构

cd /opt/redis-stable
mkdir -p config data

Step 2:Master 配置(6379)

cp redis.conf config/redis-6379.conf
vim config/redis-6379.conf

关键配置项:

daemonize yes
port 6379
logfile "/opt/redis-stable/data/6379.log"
dbfilename "dump-6379.rdb"
dir "/opt/redis-stable/data"
# 生产环境建议加上:
# bind 127.0.0.1
# protected-mode yes
# requirepass YOUR_PASSWORD

Step 3:Replica 配置(6380)

cp config/redis-6379.conf config/redis-6380.conf
vim config/redis-6380.conf

关键配置项(在 6379 基础上修改):

port 6380
logfile "/opt/redis-stable/data/6380.log"
dbfilename "dump-6380.rdb"

# 声明主节点
replicaof 127.0.0.1 6379

# 如果主节点设置了密码,从节点也要配:
# replicaauth YOUR_PASSWORD

# 从节点只读(默认开启,显式确认)
replica-read-only yes

版本注意:Redis 5.0+ 用 replicaof,旧版本用 slaveof,两者等价。

Step 4:启动

# 先启动 Master
redis-server config/redis-6379.conf

# 再启动 Replica
redis-server config/redis-6380.conf

Step 5:验证

# 查看 Master 状态
redis-cli -p 6379 info replication
# role:master
# connected_slaves:1

# 查看 Replica 状态
redis-cli -p 6380 info replication
# role:slave
# master_link_status:up

# 写数据验证
redis-cli -p 6379 set hello world
redis-cli -p 6380 get hello
# "world"  ← 同步成功!

# 验证只读
redis-cli -p 6380 set foo bar
# (error) READONLY You can't write against a read only replica.

动态修改主从关系(无需改配置文件)

# 让 6380 改挂到另一个 Master
redis-cli -p 6380 REPLICAOF 10.0.0.2 6379

# 解除主从关系,变回独立 Master
redis-cli -p 6380 REPLICAOF NO ONE

⚠️ REPLICAOF 是异步命令,执行后 Replica 会断开与原 Master 的连接,重新连接新 Master。

相关卡片


参考卡:主从复制核心配置参数

一句话:需要改什么配置?这张表就够了。

Master 侧参数

参数默认值说明
repl-backlog-size1MB部分复制的环形缓冲区大小
repl-backlog-ttl3600秒backlog 中无人使用时,多久后释放
min-replicas-to-write0Master 接受写入的最小从节点数(0=不限制)
min-replicas-max-lag10秒从节点最大延迟,超过此值 Master 拒绝写入
replica-announce-ip自动向其他节点宣告的 IP(容器环境常用)
replica-announce-port自动向其他节点宣告的端口

Replica 侧参数

参数默认值说明
replicaof无master_ip master_port,声明主节点
replicaauth无主节点密码(如果主节点开启了 requirepass)
replica-read-onlyyes从节点是否只读
replica-priority100哨兵选举新 Master 的优先级(越低越优先)
replica-serve-stale-datayes断连时是否继续提供服务(yes=返回旧数据)
replica-repl-diskless-syncno是否启用无磁盘同步(直接网络传输 RDB)
repl-diskless-syncno全局:是否启用无磁盘同步
repl-diskless-sync-delay5秒无磁盘同步前的等待延迟(等更多 Replica 一起连)

常用诊断命令

# 查看复制状态(Master / Replica 都可用)
redis-cli INFO REPLICATION

# 查看 Master 的 run_id
redis-cli INFO SERVER | grep run_id

# 查看 Master 的详细日志
tail -f /opt/redis-stable/data/6379.log

INFO REPLICATION 关键字段解读

Master 输出:

role:master                  # 角色
connected_slaves:1           # 连接的从节点数
slave0:ip=127.0.0.1,port=6380,state=online,offset=5212,lag=1  # 从节点详情
master_repl_offset:5212      # 已同步的总偏移量
repl_backlog_active:1        # backlog 是否激活
repl_backlog_size:1048576    # backlog 大小

Replica 输出:

role:slave                   # 角色
master_host:127.0.0.1        # 主节点 IP
master_port:6379             # 主节点端口
master_link_status:up        # 连接状态
master_last_io_seconds_ago:4 # 上次 IO 距今秒数
master_sync_in_progress:0    # 是否正在全量同步
slave_repl_offset:5212       # 已同步偏移量

相关卡片


故障卡:常见问题与排查

一句话:主从复制出问题,先看 INFO REPLICATION,再看日志。

问题 1:从节点连不上主节点

现象:master_link_status:down

排查步骤:

# 1. 检查网络
ping <master_ip>
telnet <master_ip> <master_port>

# 2. 检查主节点是否监听
redis-cli -h <master_ip> -p <master_port> PING

# 3. 检查主节点日志
tail -100 /opt/redis-stable/data/6379.log

# 4. 检查防火墙 / 安全组

常见原因:

  • 主节点 bind 配置限制了来源 IP
  • protected-mode yes 但未设密码
  • 防火墙阻断了端口

问题 2:全量复制反复触发

现象:日志中频繁出现 Full resync requested

排查:

# 检查 repl_backlog 是否太小
redis-cli INFO REPLICATION | grep repl_backlog

# 检查主从延迟
redis-cli INFO REPLICATION | grep lag

常见原因:

  • repl-backlog-size 太小,增量数据被覆盖
  • 网络不稳定导致频繁断连
  • 主节点数据量太大,RDB 传输时间过长

解决:增大 repl-backlog-size(默认 1MB,建议 64MB+)

问题 3:从节点写入报错

现象:

(error) READONLY You can't write against a read only replica.

这是正常行为。Replica 默认只读,不要在上面写数据。

如果需要让某个节点变回 Master:

redis-cli -p 6380 REPLICAOF NO ONE

⚠️ 执行后该节点会清空自己与主节点不一致的数据,重新同步。

问题 4:主节点宕机后的数据一致性

场景:Master 突然挂了,Replica 上有旧数据。

行为:

  • Replica 在 replica-serve-stale-data yes(默认)时,断连期间继续服务旧数据
  • 恢复连接后,会根据 run_id 判断是否全量同步
  • 如果 Master 重启后 run_id 变化,Replica 会触发全量复制

建议:配合 #概念卡:全量复制 vs 部分复制 中的部分复制机制,缩短恢复时间。

问题 5:大 RDB 传输阻塞主节点

场景:数据量大时,BGSAVE fork 子进程 + 传输 RDB 消耗大量资源。

优化方案:

方案配置说明
无磁盘同步repl-diskless-sync yesRDB 直接通过网络传输,不经磁盘
延迟同步repl-diskless-sync-delay 10等 10 秒,让更多 Replica 一起加入,一次性传输
降低 fork 影响增加内存、使用 SSD减少 CoW 开销

日志关键词速查

日志关键词含义
Replica asks for synchronizationReplica 请求同步
Full resync requested全量复制请求
Partial resynchronization not possible无法部分复制,退化为全量
Background RDB transfer started开始传输 RDB
MASTER <-> REPLICA sync: Finished with success同步成功
Connection with replica lost与 Replica 连接断开

相关卡片


类比卡:用"图书馆"理解主从复制

一句话:主从复制就像图书馆馆长把藏书复印给分馆。

角色映射

Redis 概念图书馆类比
Master总馆馆长,拥有唯一正版藏书
Replica分馆管理员,只有复印件
RDB 文件整馆藏书目录的复印本
repl_backlog馆长的小本子,记录最近借出去的书
run_id馆长的工牌编号
PSYNC分馆问:“我要你的书,是全馆目录还是要补借的书?”

场景 1:新开分馆(全量复制)

分馆管理员第一天上班,问馆长:“把我的书给你。”

馆长说:“好,我先把你需要的全部藏书目录复印一遍(BGSAVE),然后给你送过去。”

复印期间有新读者借走了几本书,馆长在小本子上记下来(repl_backlog)。

送完目录后,馆长再把小本子上记的几本书的借阅信息补上。

分馆拿到完整目录后,清空自己空荡荡的书架,按照目录摆好书。

场景 2:分馆短暂离开又回来(部分复制)

分馆管理员出去吃了个午饭,回来问:“我走的时候你给了我到第 500 页,之后发生了什么?”

馆长翻开小本子一看:“哦,你走后我新增了 3 条借阅记录,给你。”

不需要重新复印整本目录,因为小本子(repl_backlog)里还记着。

场景 3:馆长换人了(run_id 变化)

分馆管理员回来,发现馆长换了——工牌编号(run_id)不一样了。

管理员心想:“这肯定是新馆长,我得要全套新书目。”

于是触发全量复制。旧馆长的复印本作废,重新来过。

场景 4:小本子写满了(backlog 溢出)

分馆管理员出去了三天才回来。

馆长的小本子只有 1MB,三天产生的借阅记录早就把旧记录覆盖了。

管理员:“那我走的时候到回来之间发生了什么?”

馆长:“对不起,小本子满了,旧记录没了。我只能给你复印全套新书目。”

解决方法:把小本子(repl-backlog-size)换大一点。

核心原则

只读:分馆管理员不能改藏书目录,只能读。要改?去找总馆馆长。

单向:信息从总馆流向分馆,不会反向流动。

异步:馆长复印完目录通知分馆,不等分馆确认就继续接待读者。所以偶尔会有几秒延迟。

相关卡片