Redis 持久化与复制:RDB、AOF 与主从同步
深入 Redis 数据安全的两条主线:持久化(RDB/AOF 的工作原理与选择)和主从复制(全量同步/增量同步/断点续传),以及实践中的常见坑。
目录
| 章节 | 说明 |
|---|---|
| AOF 日志工作原理 | 写后日志、三种刷盘策略、AOF 重写机制 |
| RDB 快照工作原理 | fork + Copy-on-Write、快照频率权衡 |
| 混合持久化 | 4.0+ 兼顾两者优势 |
| 主从复制原理 | 全量同步三阶段、增量同步、repl_backlog |
| 主从复制的坑 | 数据不一致、主从切换的典型问题 |
| AOF 重写机制 | 重写缓冲区的作用与新写操作处理 |
AOF 日志工作原理
写后日志(Write After Log)
AOF 是先执行命令,后写日志(与数据库 WAL 的"先写日志再执行"相反):
客户端命令 → Redis 执行 → 写入 AOF 缓冲区 → 刷盘
好处:
- 命令已被验证合法,日志中不会有错误的命令
- 不阻塞当前命令的执行(日志写在执行之后)
坏处:执行成功但还未写入 AOF 就宕机 → 这段数据丢失
三种刷盘策略
appendfsync | 触发时机 | 数据安全性 | 性能 |
|---|---|---|---|
always | 每条命令都同步刷盘 | 最高(最多丢 1 条) | 最低 |
everysec(推荐) | 每秒刷盘一次 | 高(最多丢 1 秒) | 好 |
no | 由 OS 决定 | 低 | 最高 |
生产推荐
everysec:兼顾安全性和性能,每秒刷盘由后台线程异步执行,不阻塞主线程。
AOF 文件可能的阻塞点
always模式:每次命令都刷盘,fsync 调用频繁everysec模式:后台线程正在 fsync 时,主线程再次调用 write 会等待上一次 fsync 完成
RDB 快照工作原理
BGSAVE 与 fork
主进程 → fork() → 子进程(负责将内存数据序列化写入 dump.rdb)
主进程继续处理请求
Copy-on-Write(写时复制):
- fork 后父子进程共享同一份内存页
- 主进程修改某个内存页时,OS 复制该页给主进程,子进程继续读原始页
- 子进程看到的始终是 fork 时刻的内存快照
快照频率的权衡
频率高 → 数据丢失少 → 但 fork 开销大,可能阻塞主线程
频率低 → fork 开销小 → 但宕机后恢复时间长,丢失数据多
fork 的开销:fork 本身是 O(1) 的(只复制页表,不复制数据),但数据量越大,页表越大,fork 越慢(通常几十毫秒到几百毫秒)。
RDB 文件格式(源码角度)
REDIS | db_version | databases | EOF | check_sum
5B 4B 多个DB 8B
每个 DB:
SELECTDB | db_number | key_value_pairs
每个 key_value_pair:
[EXPIRETIME_MS + 过期时间] | type | key | value
可用
redis-check-rdb dump.rdb验证 RDB 文件完整性。
混合持久化
Redis 4.0+ 引入,开启方式:
aof-use-rdb-preamble yes
AOF 文件结构:
[RDB 格式的全量数据] + [增量 AOF 日志]
AOF 重写时,将当前内存数据以 RDB 格式写入文件头部,后续增量变更以 AOF 格式追加。
优势:
- 恢复速度接近 RDB(头部是紧凑的二进制格式)
- 数据安全性接近 AOF(尾部的增量日志)
- 生产环境推荐开启
主从复制原理
全量同步(第一次同步)三阶段
阶段1:建立连接
从库 → 主库:PSYNC ? -1(runID 未知,offset=-1)
主库 → 从库:+FULLRESYNC {runID} {offset}
阶段2:主库发送 RDB
主库执行 BGSAVE 生成 RDB,同时将新写入命令存入 replication buffer
主库将 RDB 文件发送给从库
从库清空本地数据,加载 RDB
阶段3:发送增量命令
主库将 replication buffer 中积累的命令发送给从库
之后持续发送增量写命令
增量同步(断点续传)
从库重连后,发送 PSYNC {runID} {offset}:
主库检查:
1. runID 是否匹配(防止主库已切换)
2. offset 是否在 repl_backlog(环形缓冲区)范围内
如果两者都满足 → 增量同步(只发 offset 之后的命令)
否则 → 全量同步
repl_backlog_buffer(复制积压缓冲区):
- 主库维护的固定大小环形缓冲区(默认 1MB)
- 保存最近写入的命令,供从库断连重连后做增量同步
- 如果 offset 对应的数据已被覆盖,必须做全量同步
# 适当调大,避免网络抖动时频繁全量同步
repl-backlog-size 10mb
复制相关配置
# redis.conf(从库)
replicaof 主库IP 主库端口
masterauth 主库密码
# 从库只读
replica-read-only yes
# 从库断连时是否继续服务旧数据(yes=服务过期数据,no=返回错误)
replica-serve-stale-data yes
# 调大复制积压缓冲区(防止短暂断连触发全量同步)
repl-backlog-size 10mb
主从复制的坑
坑 1:从库崩溃重启 → 触发全量同步
从库重启后 runID 改变,主库认为这是新实例,强制全量同步。
- 影响:从库需要加载大量 RDB,期间无法服务读请求
- 解决:Redis 4.0+ 支持
psync2,崩溃重启也能增量同步(通过持久化 replid 到磁盘)
坑 2:网络闪断 → repl_backlog 被覆盖 → 全量同步
网络断连超过 repl_backlog 能覆盖的时间,触发全量同步。
- 解决:增大
repl-backlog-size
坑 3:主从不一致(过期 key)
从库上的 key 过期了,但在从库上查询可能还会返回(从库只执行 DEL 命令,不主动删除过期 key):
- Redis 3.2+ 修复:从库会检查 key 的过期时间,已过期则返回空(即使还没收到主库的 DEL)
坑 4:主从切换时数据丢失
主库宕机,但部分数据还未同步到从库,新主库(原从库)没有这部分数据:
- 解决:
min-replicas-to-write 1+min-replicas-max-lag 10(至少 1 个从库确认,且延迟 ≤ 10 秒,才允许写入)
坑 5:主库生成 RDB 期间宕机
BGSAVE 的 RDB 文件未生成完,主库宕机,从库收到不完整的 RDB → 从库会拒绝加载(有校验和保护)。
AOF 重写机制
为什么需要重写
AOF 追加记录每一条写命令,会有大量冗余(如 INCR 执行了 100 次,AOF 有 100 条,但只需要 1 条 SET)。重写后只保留能恢复当前数据状态的最小命令集。
重写流程
主进程 fork() 子进程
↓
子进程:读取当前内存数据,生成重写后的新 AOF 文件
↓(同时)
主进程:继续处理请求,将新写入命令追加到:
1. 旧 AOF 文件(保证极端情况下旧文件可用)
2. AOF 重写缓冲区(新写入命令)
↓
子进程完成 → 主进程将重写缓冲区的命令追加到新 AOF 文件
↓
原子替换旧 AOF 文件(rename 系统调用)
AOF 重写缓冲区(aof_rewrite_buf)是关键:确保重写期间新写入的命令不丢失,且最终被合并进新文件。
触发条件
# 自动触发(两个条件同时满足)
auto-aof-rewrite-percentage 100 # 比上次重写后增长 100%
auto-aof-rewrite-min-size 64mb # 文件大小至少 64MB
# 手动触发
BGREWRITEAOF
参考资料
- 《Redis 核心技术与实战》— 第 4-6、32 讲(蒋德钧)
- 《Redis 源码剖析与实战》— 第 18-20 讲(蒋德钧)
- Redis Persistence
- Redis Replication
评论 (0)