Redis 主从同步机制 Redis 主从同步是实现高可用和读写分离的基础,包含两个核心阶段:全量复制 和增量复制 。
核心架构 1 2 3 4 5 6 7 8 9 10 11 12 主库(Master) ├─ 处理写命令 ├─ 处理读命令 ├─ 生成 RDB 快照 ├─ 维护复制缓冲区 └─ 向从库同步数据 从库(Slave/Replica) ├─ 接收主库数据 ├─ 只读模式(默认) ├─ 可以级联复制 └─ 支持读写分离(需配置)
阶段一:全量复制(Full Resynchronization) 触发时机
从库首次连接主库
从库断线时间过长,复制缓冲区数据已被覆盖
手动执行 SLAVEOF 或 REPLICAOF 命令
完整流程 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 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
查看 COW 信息 1 2 3 4 5 127.0.0.1:6379> INFO stats cow_memory: 4523456 rdb_last_cow_size: 4523456
排查同步延迟 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 INFO replication INFO replication redis-cli --stat SLOWLOG GET 10
常见问题 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 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 = 智能选择复制方式的协议