前言
在前三篇文章中,我们拆解了Redis的数据结构、内存管理和缓存三大问题。但还有一个根本性问题没有解决:Redis是内存数据库,数据存在内存中。一旦宕机,内存中的数据全部丢失。
这就是Redis持久化要解决的问题。面试中,这个问题是必考题:
“RDB和AOF有什么区别?”
“为什么生产环境要同时开RDB和AOF?”
“AOF重写是什么?为什么要重写?”
本文从RDB的Copy-On-Write原理出发,拆解AOF的三种刷盘策略,最后落到生产环境的最佳配置方案。
本文核心问题:
- RDB的工作原理是什么?bgsave为什么用fork而不是新建线程?
- Copy-On-Write在RDB中是怎么体现的?
- AOF的三种刷盘策略各有什么优劣?生产环境选哪个?
- AOF重写是什么?为什么要重写?什么时候触发?
- RDB和AOF各自的优劣?为什么生产环境要同时开?
- RDB+AOF混合模式是怎么工作的?
读完本文,你将对Redis的持久化机制拥有从原理到配置的完整理解。
一、为什么需要持久化?
疑问:Redis不是缓存吗?数据丢了重新加载不就行了?
回答:Redis不只是缓存。它常被用作分布式锁、消息队列、计数器、Session存储——这些数据丢了不只是"多查一次数据库",而是可能导致业务逻辑错误。
场景一:Redis做分布式锁 宕机 → 锁全部丢失 → 多个线程可能同时获得同一把锁 → 超卖 场景二:Redis做库存计数器 宕机 → 扣减记录全部丢失 → 已售库存回退 → 数据不一致 场景三:Redis做Session存储 宕机 → 所有用户登录状态丢失 → 大量用户被迫重新登录持久化的本质:把内存数据备份到磁盘。宕机后重启,从磁盘恢复数据到内存。Redis提供两种持久化方式——RDB(快照)和AOF(追加日志),各有优劣,生产环境通常两者配合使用。
二、RDB——内存快照
疑问:RDB是什么?它是怎么工作的?
回答:RDB是某个时间点的内存全量快照。就像给内存拍一张照片,宕机后从这张照片恢复数据。
2.1 save vs bgsave
| 命令 | 执行方式 | 是否阻塞 | 原理 |
|---|---|---|---|
| save | 主线程执行 | 阻塞所有命令 | 主线程遍历整个内存写盘,期间无法处理任何请求 |
| bgsave | fork子进程执行 | 不阻塞主线程 | 子进程写盘,主线程继续处理请求 |
生产环境绝对不能用save——生成快照期间Redis完全不响应,如果内存有10GB,阻塞时间可能长达数十秒。bgsave通过fork子进程将写盘工作交给子进程处理,主线程只在fork的瞬间有短暂的停顿。
2.2 bgsave的工作流程
1. 主线程收到bgsave命令 2. 主线程调用fork()创建子进程 3. fork期间主线程短暂阻塞(通常微秒到毫秒级,取决于系统负载) 4. fork完毕 → 主线程继续处理客户端命令 5. 子进程遍历整个内存空间 → 写RDB文件 6. 子进程完成 → 通知主线程 → 子进程退出关键操作:fork通过Copy-On-Write机制让子进程和父进程共享同一块物理内存,不需要复制整个内存空间。
2.3 Copy-On-Write——fork为什么高效
fork之后,父子进程共享同一块物理内存。操作系统将内存页标记为只读。当主线程需要修改某个内存页时,CPU触发写保护异常,操作系统为这个页创建一个副本给主线程写入,原来的页仍属于子进程用于快照。子进程读取的是fork那一刻的"冻结快照",主线程继续写入不影响快照的完整性。
fork时刻: 父进程内存页A:[值=100] ← 父子共享 主线程执行 SET key 200: 操作系统复制页A → 页A'(副本) 父进程写页A':[值=200] 子进程读页A:[值=100] ← 冻结在fork时刻 关键: 只有被修改的页才会复制(Copy-On-Write) 没有被修改的页仍然共享 所以fork不消耗内存,后续主线程写入时按页粒度复制fork的优势:高效利用内存、不阻塞主线程、快照数据天然一致。代价是如果主线程在生成快照期间大量写入,Copy-On-Write导致的额外内存开销会增长——所有被修改的页都要复制一份。
2.4 RDB的触发方式
| 触发方式 | 做法 | 示例配置 |
|---|---|---|
| 手动触发 | 执行BGSAVE命令 | — |
| 自动触发 | 配置文件中设置条件 | save 900 1(900秒内至少1次修改触发) |
| 主从复制 | 从库全量同步时,主库执行bgsave | — |
三、AOF——追加写入日志
疑问:RDB是全量快照,AOF是什么?
回答:AOF是增量日志——记录每一次写操作,重启时按顺序重放这些命令来恢复数据。
3.1 AOF的工作流程
1. 客户端执行 SET key value 2. Redis执行这个命令(修改内存中的数据) 3. Redis将这条命令追加到AOF缓冲区 4. AOF缓冲区根据刷盘策略写入磁盘文件AOF文件中的内容:
*3 $3 SET $3 key $5 value3.2 三种刷盘策略
appendfsync配置控制AOF何时写入磁盘:
| 策略 | 做法 | 安全性 | 性能 |
|---|---|---|---|
| always | 每条命令都刷盘 | 最高,一条不丢 | 最低,每次写盘等待磁盘完成 |
| everysec | 每秒刷一次缓冲区 | 折中,最多丢1秒数据 | 较好,只保证每秒一次磁盘同步 |
| no | 交给操作系统决定 | 最低 | 最高,完全无磁盘写入等待 |
生产环境选everysec:丢1秒数据在绝大多数场景下可接受,而always的磁盘等待对Redis性能惩罚太大——每次写入都等待磁盘操作完成,Redis的单线程模型意味着这段时间内其他所有命令都被阻塞。no不保证数据何时落盘,操作系统级别的写延迟可能从几十毫秒到几秒不等,宕机时丢失的数据量完全不可控。
3.3 AOF重写——解决日志膨胀问题
问题:AOF记录每条写命令,日积月累文件越来越大。一个计数器被INCR了1000次,AOF中就记录1000条INCR。重启后重放1000条命令来恢复一个计数器,效率极低。
解决:AOF重写——根据当前内存状态,生成最小的写命令集合。
重写前AOF(1000条): INCR counter INCR counter INCR counter ...(1000次) 重写后AOF(1条): SET counter 1000重写过程:
- Redis fork一个子进程
- 子进程根据当前内存状态生成新的AOF文件
- 主线程在重写期间的写操作,同时记录到旧的AOF缓冲区和重写缓冲区
- 子进程完成重写 → 将重写缓冲区中的增量追加到新AOF文件
- 用新AOF文件替换旧AOF文件
触发条件:
auto-aof-rewrite-percentage 100 # AOF文件增长超过100%时触发重写 auto-aof-rewrite-min-size 64mb # AOF文件至少64MB才考虑重写四、RDB vs AOF
| 维度 | RDB | AOF |
|---|---|---|
| 持久化方式 | 全量快照 | 增量日志 |
| 文件大小 | 小(压缩的二进制) | 大(明文命令,重写后会缩小) |
| 恢复速度 | 快(直接加载) | 慢(逐条重放命令) |
| 数据安全性 | 较低(两次快照之间的数据可能丢失) | 较高(每秒刷盘最多丢1秒数据) |
| 对性能影响 | fork瞬间有微秒级阻塞 | everysec时性能损耗小 |
| 适用场景 | 备份、快速恢复 | 数据安全性要求高 |
五、为什么生产环境要同时开?
疑问:既然各有优劣,为什么不用一个最好的方案?
回答:两者互补——RDB用于快速恢复,AOF用于数据安全。同时开RDB和AOF,相当于有了"整点快照+分钟级日志"的双重保险。
场景一:Redis正常重启 → 用AOF恢复(数据最新,一条不丢) 场景二:AOF文件损坏(磁盘坏道) → 用RDB恢复(最近5分钟的快照,比全部丢失强) 场景三:从备份恢复数据到新集群 → 用RDB快速恢复全量数据,AOF补充增量 场景四:AOF体积过大影响重启速度 → 用RDB提供基准,AOF只记录RDB之后的增量混合持久化(Redis 4.0+):
RDB做全量快照,AOF做增量改进。RDB解决了"恢复慢"的问题(全量数据直接加载),AOF解决了"数据全"的问题(RDB时刻之后到当前的所有增量都被记录,不会丢失上次RDB之后的数据)。
六、生产环境的最佳配置
# redis.conf # 开启RDB save 900 1 # 900秒内至少1次修改 → 触发bgsave save 300 10 # 300秒内至少10次修改 → 触发bgsave save 60 10000 # 60秒内至少10000次修改 → 触发bgsave # 开启AOF appendonly yes appendfsync everysec # 每秒刷盘 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 开启混合持久化 aof-use-rdb-preamble yes # AOF文件前半部分是RDB快照,后半部分是增量命令混合持久化的工作方式:重写AOF时,先把当前内存数据以RDB格式写入AOF文件前半部分,再把重写期间的增量命令以AOF格式追加在后。重启时先快速加载RDB,再重放增量AOF。既有了RDB的快速恢复能力,又有了AOF的完整性保证。
总结
- RDB是内存全量快照,bgsave通过fork子进程+Copy-On-Write实现不阻塞主线程的写盘操作。快照间有丢失窗口
- AOF是增量日志,记录每次写操作。三种刷盘中everysec是性能和安全的最优折中——丢1秒数据可接受,磁盘同步的开销也可控
- AOF重写不阻塞主线程——fork子进程生成最小化的新AOF文件,替代旧AOF,解决日志膨胀问题
- 生产环境必须同时开:RDB做整点的快速恢复基准,AOF做分钟级的增量数据保护。两者互补,宕机后可用最近的备份组合恢复
- 混合持久化:RDB+增量AOF的组合方式兼得RDB的恢复速度和AOF的完整性,是最高效的生产配置
- 持久化策略的选择最终由恢复时间目标和数据丢失容忍度两个阈值决定:能接受丢多少数据、能接受恢复花了多久
下一篇预告:Redis原理(五)——主从复制与哨兵机制:Redis高可用的基石。拆解全量复制与部分复制的原理、主观下线和客观下线的区别、哨兵集群的选主过程,以及和Cluster的关系。