Redis 主从同步机制

Redis 主从同步是实现高可用和读写分离的基础,包含两个核心阶段:全量复制增量复制

核心架构

1
2
3
4
5
6
7
8
9
10
11
12
主库(Master)
├─ 处理写命令
├─ 处理读命令
├─ 生成 RDB 快照
├─ 维护复制缓冲区
└─ 向从库同步数据

从库(Slave/Replica)
├─ 接收主库数据
├─ 只读模式(默认)
├─ 可以级联复制
└─ 支持读写分离(需配置)

阶段一:全量复制(Full Resynchronization)

触发时机

  • 从库首次连接主库
  • 从库断线时间过长,复制缓冲区数据已被覆盖
  • 手动执行 SLAVEOFREPLICAOF 命令

完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1. 从库连接主库

2. 从库发送 PSYNC 命令(首次发送 PSYNC ? -1)

3. 主库收到 PSYNC,触发 BGSAVE

4. 主库生成 RDB 快照文件
├─ 调用 fork() 创建子进程
├─ 子进程生成 RDB 文件
└─ 主进程继续处理客户端请求

5. 主库将 RDB 文件发送给从库
├─ 有盘模式:先写磁盘,再发送
└─ 无盘模式:直接通过 socket 发送

6. 从库接收 RDB 文件
├─ 清空自己的数据
└─ 加载 RDB 文件

7. 主库发送复制缓冲区的命令
├─ 生成 RDB 期间积累的写命令
└─ 保证从库追平到最新状态

8. 从库执行缓冲命令

9. 同步完成,进入增量复制阶段

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
时间线:
T0: 主库数据: SET a 1, SET b 2, SET c 3
T1: 从库连接,发送 PSYNC ? -1
T2: 主库开始 BGSAVE(此时主库继续接收命令)
T3: 客户端执行: SET d 4, SET e 5
→ 主库执行并写入复制缓冲区
T4: RDB 生成完成(包含 a, b, c)
T5: 主库发送 RDB 给从库
T6: 从库清空数据,加载 RDB(现在有 a, b, c)
T7: 主库发送缓冲区命令: SET d 4, SET e 5
T8: 从库执行缓冲命令(现在有 a, b, c, d, e)
T9: 同步完成,进入增量复制

两种 RDB 传输模式

1. 有盘同步(Disk-backed Sync)

1
2
3
4
5
6
7
8
9
10
主库流程:
1. BGSAVE 生成 RDB 文件到磁盘
2. 读取磁盘上的 RDB 文件
3. 通过 socket 发送给从库

优点:可以服务多个从库(一份 RDB 文件)
缺点:需要磁盘 IO,速度较慢

配置:
repl-diskless-sync no # 默认模式

2. 无盘同步(Diskless Sync)

1
2
3
4
5
6
7
8
9
10
11
主库流程:
1. BGSAVE 生成 RDB
2. 不写磁盘,直接通过 socket 发送给从库
3. 需要延迟启动(等待多个从库连接)

优点:无需磁盘 IO,速度快
缺点:每次只能服务一个从库(或少量)

配置:
repl-diskless-sync yes
repl-diskless-sync-delay 5 # 等待 5 秒,让多个从库连接

阶段二:增量复制(Incremental Replication)

核心概念

增量复制不是发送 AOF 文件,而是实时发送写命令流!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
主库执行命令的流程:
客户端发送: SET key value

主库:
├─ 执行命令: SET key value
├─ 写入 AOF 文件(如果开启)
├─ 写入复制缓冲区
├─ 实时发送给从库(增量复制)
└─ 返回客户端: +OK

从库:
├─ 接收命令: SET key value
├─ 执行命令: SET key value
└─ 不返回结果

命令传播协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
发送的命令使用 Redis 协议(RESP)格式:

SET key value 的传输格式:
*3 # 3个参数
$3 # 第一个参数长度
SET # 命令
$3 # 第二个参数长度
key # key
$5 # 第三个参数长度
value # value

这和 AOF 文件中的格式完全相同!
区别:
- AOF:写入文件
- 增量复制:实时通过网络发送给从库

复制缓冲区(Replication Buffer)

1
2
3
4
5
6
7
8
9
10
11
12
主库内存中的关键结构:
┌─────────────────────────────────┐
│ Replication Buffer │
│ ├─ 存储待发送给从库的命令 │
│ ├─ 基于环形缓冲区(repl_backlog)│
│ ├─ 大小可配置 │
│ └─ 用于断线重连后的增量复制 │
└─────────────────────────────────┘

配置:
repl-backlog-size 1mb # 默认 1MB
repl-backlog-ttl 3600 # 从库断开后缓冲区保留时间

环形缓冲区工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
场景:从库断线重连

时间线:
T1: 主库执行命令 C1, C2, C3, C4, C5
都写入 repl_backlog
缓冲区: [C1][C2][C3][C4][C5]

写入位置

T2: 从库断线(停在 C2,offset=2)

T3: 从库重连,发送 PSYNC offset=2
意思是:我已经有 C1, C2,从 C3 开始发

T4: 主库检查 repl_backlog
如果 C3, C4, C5 还在缓冲区:
→ 增量复制:只发送 C3, C4, C5
→ 快速!耗时毫秒级

T5: 如果缓冲区已满,C3 被覆盖:
缓冲区: [C6][C7][C3被覆盖][C4][C5]
→ 无法增量复制
→ 触发全量复制:重新生成 RDB
→ 慢!耗时秒级或分钟级

全量 vs 增量对比

维度 全量复制 增量复制
触发时机 首次同步、缓冲区不足 短时间断线重连
传输内容 RDB 文件 + 缓冲命令 仅缺失的写命令
速度 慢(生成+传输 RDB) 快(只传少量命令)
网络开销 大(传输完整数据) 小(只传差异)
从库状态 清空数据,重新加载 保持现有数据
CPU 开销 高(fork + COW) 低(仅发送命令)
类似机制 完整备份恢复 类似 MySQL binlog 回放

PSYNC 命令详解

首次同步

1
2
3
4
5
6
7
8
9
10
11
从库发送:PSYNC ? -1
含义:
- ? = 不知道主库的运行 ID
- -1 = 没有复制偏移量

主库响应:
+FULLRESYNC <runid> <offset>
- runid: 主库的唯一标识
- offset: 当前复制偏移量

然后开始全量复制

断线重连

1
2
3
4
5
6
7
8
9
10
11
12
13
从库发送:PSYNC <runid> <offset>
含义:
- runid: 上次连接的主库 ID
- offset: 已同步到的位置

主库检查:
1. runid 是否匹配?
- 不匹配 → 主库可能变了 → 全量复制
- 匹配 → 继续检查

2. offset 是否在 repl_backlog 中?
- 在 → 增量复制:发送缺失的命令
- 不在 → 缓冲区已覆盖 → 全量复制

级联复制

1
2
3
4
5
6
7
8
9
10
11
12
架构:
Master → Replica1 → Replica2 → Replica3

优点:
- 减轻主库压力
- 支持更多从库
- 跨机房同步

配置:
Replica1 的 slaveof 指向 Master
Replica2 的 slaveof 指向 Replica1
Replica3 的 slaveof 指向 Replica2

关键配置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# redis.conf

# ==================== 全量复制相关 ====================
repl-diskless-sync no # 是否无盘同步
# no = 有盘(默认)
# yes = 无盘
repl-diskless-sync-delay 5 # 无盘同步延迟(秒)

# ==================== 增量复制相关 ====================
repl-backlog-size 1mb # 复制缓冲区大小
# 建议根据写入量调整
repl-backlog-ttl 3600 # 缓冲区保留时间(秒)
# 从库断开后多久清除

# ==================== 性能优化 ====================
repl-disable-tcp-nodelay no # 是否禁用 TCP_NODELAY
# no = 立即发送(低延迟,推荐)
# yes = 合并发送(省带宽)

repl-timeout 60 # 复制超时时间(秒)

# ==================== 从库配置 ====================
replica-serve-stale-data yes # 断线期间是否提供旧数据
replica-read-only yes # 从库是否只读(推荐)

监控与排查

查看复制状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 主库执行
127.0.0.1:6379> INFO replication

# 输出示例:
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=12345,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=12340,lag=1

# 关键字段:
# - offset: 复制偏移量(主从差值应很小)
# - lag: 延迟秒数
# - state: online/bgsave/wait_bgsave/send/wait_online

查看 COW 信息

1
2
3
4
5
127.0.0.1:6379> INFO stats

# 相关指标:
cow_memory: 4523456 # COW 消耗的内存(字节)
rdb_last_cow_size: 4523456 # 上次 RDB 的 COW 大小

排查同步延迟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1. 查看主从 offset 差值
INFO replication
# master_repl_offset - slave_repl_offset = 差值

# 2. 查看从库延迟
INFO replication
# slaveX:lag=1 ← 延迟 1 秒

# 3. 查看网络带宽
redis-cli --stat

# 4. 查看慢查询
SLOWLOG GET 10

# 常见原因:
# - 网络带宽不足
# - 主库写入量过大
# - 从库负载过高
# - RDB 生成频繁

常见问题

1. 频繁全量复制

1
2
3
4
5
6
7
8
9
10
11
12
原因:
- repl-backlog-size 太小
- 从库断线时间过长
- 网络不稳定

解决:
1. 增大复制缓冲区:
repl-backlog-size 64mb

2. 优化网络稳定性

3. 监控 offset 差值

2. 复制缓冲区内存占用过高

1
2
3
4
5
6
7
8
原因:
- 从库断线但未超时
- 缓冲区持续增长

解决:
1. 设置合理的 repl-backlog-ttl
2. 监控内存使用
3. 及时清理断开的从库

3. 无盘同步失败

1
2
3
4
5
6
7
8
原因:
- 从库连接时间不一致
- 网络延迟差异大

解决:
1. 调整 repl-diskless-sync-delay
2. 使用有盘同步模式
3. 检查网络稳定性

最佳实践

1. 合理设置缓冲区大小

1
2
3
4
5
6
# 根据写入量计算:
# 缓冲区大小 = 每秒写入量 × 最大允许断线时间

# 示例:
# 每秒写入 1MB,允许断线 60 秒
repl-backlog-size 64mb # 留一些余量

2. 监控复制延迟

1
2
3
4
5
6
7
8
9
# 脚本监控(示例)
#!/bin/bash
offset=$(redis-cli INFO replication | grep master_repl_offset | cut -d: -f2 | tr -d '\r')
slave_offset=$(redis-cli -p 6380 INFO replication | grep slave_repl_offset | cut -d: -f2 | tr -d '\r')
lag=$((offset - slave_offset))

if [ $lag -gt 1000000 ]; then
echo "复制延迟过大: $lag bytes"
fi

3. 避免全量复制高峰

1
2
3
4
5
策略:
1. 错峰执行 BGSAVE
2. 使用无盘同步减少磁盘压力
3. 控制从库数量(或使用级联)
4. 监控 COW 内存使用

4. 读写分离配置

1
2
3
4
5
6
7
# 从库配置
replica-read-only yes # 只读模式
replica-serve-stale-data yes # 同步期间提供旧数据

# 应用层:
# 写操作 → 主库
# 读操作 → 从库(负载均衡)

总结要点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1. 全量复制 = RDB 快照 + 缓冲命令回放
- 用于首次同步或缓冲区不足
- 耗时长,开销大

2. 增量复制 = 实时写命令流
- 格式同 AOF,但不是发文件
- 用于断线重连或正常同步
- 快速,开销小

3. Replication Buffer = 增量复制的关键
- 环形缓冲区存储待发送命令
- 大小要合理设置

4. 优先增量,失败才全量
- 通过 PSYNC 机制智能选择
- 减少不必要的 RDB 生成

5. 监控是关键
- 关注 offset 差值
- 监控 lag 延迟
- 及时处理异常

简单记忆:

  • 全量 = 快照传输 + 命令追赶
  • 增量 = 实时命令流(类似 binlog)
  • Buffer = 增量复制的”时间机器”
  • PSYNC = 智能选择复制方式的协议