引言:内存数据库的永恒矛盾
Redis 作为当前最流行的 KV 数据库,其核心优势在于 内存存储 带来的纳秒级读写速度。然而,内存的易失性(Volatile)决定了它必须面对一个严肃的问题:宕机后数据如何恢复?
很多开发者在面试时能背出 “RDB 是快照,AOF 是日志”,但在生产环境中配置时却一脸茫然:
- 为什么 RDB 导致了主线程卡顿?
- 为什么 AOF 文件无限膨胀把磁盘撑爆了?
no-appendfsync-on-rewrite到底该不该开?
本文将深入 Redis 源码层面,剖析这两种持久化机制的实现原理、性能瓶颈及调优策略。
一、RDB (Redis DataBase):时间的切片
RDB 是 Redis 默认的持久化方案。它在指定的时间间隔内,将内存中的数据集快照写入磁盘。
1.1 触发机制
自动触发
在 redis.conf 中配置:
save 900 1 # 900秒内至少1个key变化save 300 10 # 300秒内至少10个key变化save 60 10000 # 60秒内至少10000个key变化注意:这三行是“或”的关系。只要满足其一,就会触发。
手动触发
- SAVE:阻塞主线程,直到 RDB 完成。**生产环境绝对禁止使用!**因为 Redis 是单线程的,SAVE 执行期间服务器将无法响应任何请求。
- BGSAVE:Fork 一个子进程在后台执行。主线程继续处理请求。这是默认行为。
1.2 底层原理:Fork 与 Copy-on-Write (CoW)
很多同学不理解:为什么 BGSAVE 不阻塞主线程?如果主线程还在写数据,快照怎么保证数据一致性?
这里用到了操作系统的 写时复制 (Copy-on-Write, CoW) 机制。
- Fork 子进程:Redis 父进程调用
fork()系统调用创建子进程。- 阻塞点:
fork()本身是阻塞的,耗时取决于页表(Page Table)的大小(即内存大小)。如果 Redis 占用了 10GB 内存,Fork 可能需要几百毫秒。
- 阻塞点:
- 共享内存:Fork 后,父子进程共享同一段物理内存。子进程开始读取内存数据并写入 RDB 文件。
- CoW 发生:在此期间,如果父进程接收到了新的 写命令(修改 Key A),操作系统不会直接修改共享内存,而是将 Key A 所在的 内存页 (Page) 复制一份给父进程修改。
- 子进程看到的依然是 Key A 的旧值(快照时刻的值)。
- 父进程修改的是新副本。
性能隐患:如果 RDB 期间写流量极大,会导致大量的内存页复制,甚至导致内存占用瞬间翻倍(OOM 风险)。
1.3 RDB 的优缺点
- 优势:
- 文件紧凑(二进制压缩),适合备份和灾难恢复。
- 加载速度快(直接反序列化)。
- 劣势:
- 数据丢失:间隔(如 5 分钟)内的变更会丢失。
- CPU/内存消耗:Fork 操作属于重型操作。
二、AOF (Append Only File):操作的流水账
AOF 以日志的形式记录服务器所处理的每一个写、删除操作。
2.1 写入流程
- 命令追加:写命令执行完后,先追加到
aof_buf内存缓冲区。 - 文件写入:调用
write()将缓冲区数据写入内核缓冲区。 - 文件同步:调用
fsync()将内核缓冲区数据刷入磁盘(这是最关键的一步)。
2.2 同步策略 (appendfsync)
- always:每条命令都 fsync。
- 安全:最多丢一条命令。
- 性能:极差(转变为 IO 密集型)。
- everysec(默认):每秒 fsync 一次。
- 折中:最多丢 1 秒数据,性能损耗小。
- no:不主动 fsync,由操作系统决定(通常 30秒)。
- 性能:最好。
- 风险:宕机丢失大量数据。
2.3 AOF 重写 (Rewrite)
随着时间推移,AOF 文件会越来越大。比如你对 count 加了 100 次,AOF 里就有 100 条 INCR 命令,但恢复时只需要 SET count 100 一条。
Redis 通过 BGREWRITEAOF 触发重写:
- Fork 子进程。
- 子进程遍历内存,构造重构后的 AOF 文件。
- AOF 重写缓冲区:在子进程重写期间,父进程收到的新写命令,不仅要写入旧 AOF,还要写入 AOF 重写缓冲区。
- 子进程写完后,父进程将重写缓冲区的数据追加到新文件,并替换旧文件。
三、混合持久化:成年人的选择
在 Redis 4.0 之前,我们只能二选一或者双开。 4.0 引入了 混合持久化 (Hybrid Persistence)。
它的核心思想是:AOF 重写时,不再把内存数据转为 AOF 指令,而是直接把 RDB 二进制数据写入 AOF 文件开头,后续增量数据依然是 AOF 文本。
文件结构:[RDB 数据] + [AOF 增量日志]
优势:
- 加载极快:前面大部分是 RDB,解析速度飞快。
- 数据安全:后面的 AOF 保证了最近的数据不丢失。
配置:
aof-use-rdb-preamble yes四、生产环境最佳实践
- 内存预留:预留 50% 内存给 Fork 时的 CoW 使用。如果 32GB 机器跑 30GB Redis,BGSAVE 时必死。
- 关闭 RDB 自动触发:如果你开启了 AOF,建议注释掉
save配置,由运维脚本在低峰期(如凌晨 3 点)手动执行BGSAVE做备份。 - AOF 配置:
appendonly yesappendfsync everysecno-appendfsync-on-rewrite yes(重要:在重写期间暂停 fsync,避免磁盘 IO 争抢导致主线程阻塞,代价是重写期间宕机可能丢 30 秒数据)。
- 监控:监控
latest_fork_usec(Fork 耗时)。如果超过 1 秒,说明 Redis 实例太大了,建议拆分集群。
总结
Redis 持久化不仅仅是一个配置项开关。它涉及到操作系统原理(Fork, CoW, Page Cache)和 IO 模型的权衡。
- 如果你只做缓存(Cache),可以关闭所有持久化,性能起飞。
- 如果你做存储(Store),请务必开启混合持久化,并做好异地冷备。
别让你的数据,成为断电后的尘埃。