持久化
RDB:内存快照
把某一时刻的全部数据以二进制压缩格式写成一个文件。
两个命令:
SAVE—— 主线程执行,阻塞所有请求,生产禁用BGSAVE—— fork 子进程写盘,主线程继续服务(默认行为,save配置触发的也是 BGSAVE)
fork 与写时复制(COW):fork() 不复制物理内存,父子进程共享同一份内存页并标记为只读。子进程遍历内存写 RDB 期间,如果主线程要修改某页,触发缺页异常,内核复制那一页给主线程改,子进程仍看到旧页。所以 RDB 是一致的快照。
:::warning fork 的两个代价
- fork 本身会阻塞主线程 —— 虽然不复制数据,但要复制页表。内存越大页表越大,10GB 实例的 fork 可能阻塞几十到上百毫秒。用
INFO stats的latest_fork_usec观测。 - 可能导致内存翻倍 —— 极端情况下(持久化期间所有页都被写过)需要两倍内存。所以实例内存不宜超过物理内存的一半。 :::
优缺点:文件紧凑、恢复快(直接加载到内存),适合做备份和全量同步;但快照之间的数据会丢,频率高了 fork 开销受不了。
AOF:追加命令日志
把每条写命令追加到文件末尾(Redis 协议格式,可读)。
写回策略 appendfsync:
| 值 | 行为 | 崩溃丢失 | 性能 |
|---|---|---|---|
always | 每条命令都 fsync | 最多一条命令 | 最差 |
everysec(默认) | 每秒 fsync 一次 | 最多 1 秒 | 折中 |
no | 交给操作系统决定 | 不可控 | 最好 |
关键细节:命令是先执行后记录(和 MySQL 的 WAL 相反)。好处是不会记录错误命令,且无需校验语法;坏处是执行完还没写盘就崩溃,这条命令就丢了,且 AOF 不能用于回滚。
AOF 重写:文件会无限膨胀(对同一个 key 自增 100 次就 100 条记录),所以需要重写 —— fork 子进程,根据当前内存数据生成最小命令集(100 次自增压成一条 SET)。
重写期间主线程的新写入会同时进AOF 缓冲区和AOF 重写缓冲区,重写完成后把重写缓冲区的内容追加到新文件末尾,再原子替换旧文件。这保证了重写期间数据不丢。
触发条件:auto-aof-rewrite-percentage 100(比上次重写后大一倍)且 auto-aof-rewrite-min-size 64mb。
混合持久化
Redis 4.0 引入 aof-use-rdb-preamble yes(7.0 起默认开启并改为 multi-part AOF):AOF 重写时,把当前数据以 RDB 格式写在文件开头,之后的增量命令以 AOF 格式追加。
这是生产推荐方案,因为它同时拿到了两者的优点:RDB 部分让恢复快,AOF 部分让数据丢失窗口小到 1 秒。
怎么选
| 场景 | 方案 |
|---|---|
| 纯缓存,数据可重建 | 关闭持久化,或只开 RDB 低频备份 |
| 不能丢数据的业务数据 | 混合持久化 + everysec |
| 需要定期全量备份 | RDB(文件小,便于传输到对象存储) |
| 对一致性要求极高 | AOF always,但要接受性能损失,且仍不保证分布式一致 |
恢复优先级:同时开启时,Redis 启动优先加载 AOF(因为通常更完整)。
:::tip 高频追问
「Redis 持久化能保证不丢数据吗?」答案是不能。即使 AOF always,主库 fsync 完成后、传给从库前宕机仍会丢;主从切换时未同步的数据也会丢。Redis 的定位是高性能缓存与数据结构服务,不能当强一致数据库用,需要强一致就上 MySQL 或引入 WAIT 命令配合业务补偿。
:::
大 key 对持久化的影响
一个 1GB 的 key 会让 RDB 写入和 AOF 重写都变慢,fork 后的 COW 内存开销也更大。排查手段:
redis-cli --bigkeys # 采样扫描各类型最大 key
redis-cli --memkeys # 按内存占用排(4.0+)
redis-cli MEMORY USAGE <key> # 单个 key 的精确占用
拆分方案:Hash 按 field hash 分片成多个 key(user:info:{id % 100});List 按时间或序号分段;String 大对象考虑不放 Redis,改放对象存储只缓存 URL。