【redis】持久化之RDB与AOF
在数字世界的脉搏中,数据是流淌的血液,而持久化则是保障系统生命力的核心机制。作为内存数据库的标杆,Redis凭借其高性能特性成为互联网架构的基石,但其「易失性」的天然属性也催生了关键命题:如何在服务重启或故障时保障数据安全?
从RDB快照的瞬时记忆到AOF日志的精准回放,再到混合持久化的智慧融合,Redis用三种递进式的方案回答了这一问题。RDB以二进制快照实现高效备份,AOF以日志追加构筑数据保险,而混合模式则通过「全量快照+增量日志」的架构,在恢复效率与数据安全之间找到黄金平衡点。
RDB持久化
RDB(Redis Database)是Redis默认的持久化方式,通过在指定的时间间隔内对内存中的全量二进制数据进行快照存储实现数据持久化。
RDB是一个非常紧凑的单一文件,它保存了某个时间点的全量数据集,非常适用于数据集的备份。保存RDB文件时父进程唯一需要做的就是调用系统函数fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
由于RDB的文件内容紧凑,对数据进行了压缩,不像AOF一样数据中存在过期的key和重复的指令,所以在恢复大的数据集的时候,RDB方式会更快一些。
核心原理
其工作原理可概括为:
-
定时快照:根据配置文件
save 900 1
等规则(900秒内有1次修改即触发),通过fork子进程异步生成dump.rdb文件 -
手动触发:支持
SAVE
(同步阻塞)和BGSAVE
(后台异步)两种指令 -
写时复制:主进程持续处理请求,子进程通过Copy-On-Write技术生成内存镜像
redis.conf
中跟rdb相关的核心配置:
# 60秒内有至少有1000个键被改动时,自动触发RDB
# 300秒内有至少有10个键被改动时,自动触发RDB
# 900秒内有至少有1个键被改动时,自动触发RDB
save 900 1
save 300 10
save 60 10000
# 异步重写失败了,会停止写入
stop-writes-on-bgsave-error yes
# 开启rdb文件压缩
rdbcompression yes
# 加载rdb和保存rdb时文件进行校验
rdbchecksum yes
# rdb存储的文件名,保存路径在下面
dbfilename dump.rdb
# 在没有持久化的情况下删除复制中使用的RDB文件,通常情况下保持默认即可。
rdb-del-sync-files no
# RBD文件保存的目录
dir /usr/local/redis/data/6379
跟rdb相关的命令:
-
save:同步阻塞生成快照。
-
bgsave:后台异步生成快照。
fork的原理
RDB时需要考虑的两个问题:
-
假如8点开始RDB,整个过程需要持续5分钟,那么在这5分钟有些key被修改了,有些key被删除了,那么这些key会不会被存储到RDB文件中去呢?
-
如果有用另外一个进程来负责持久化,那么是不是需要将整个redis进程的内存拷贝一份,时间成本会不会很大,内存空间是不是够用?
上面的这两个问题都能从fork的原理中找到答案。
fork是linux提供的一个系统调用,其声明如下:
#include <unistd.h>
pid_t fork(void);
返回值pid_t
是进程描述符,实质就是一个int,如果fork函数调用失败,返回一个负数-1,调用成功则返回两个值:0和子进程ID,对于子进程来说返回值为0,对于父进程来说返回的是子进程的ID。
下面通过一个c语言代码来演示fork的原理:
#include<stdio.h>
#include<unistd.h>
int main() {
int i = 10;
pid_t pid = fork();
if(pid > 0) {
i--;
printf("i am parent process, id:%d\n", getpid());
printf("i am parent process, i=%d\n", i);
sleep(20);
} else if(pid == 0) {
printf("i am child process, pid:%d, ppid:%d\n", getpid(), getppid());
i++;
printf("i am child process, i=%d\n", i);
sleep(20);
}
}
运行结果如下:
i am parent process, id:14848
i am parent process, i=9
i am child process, pid:14849, ppid:14848
i am child process, i=11
说明:
-
fork系统调用之后,父进程和子进程一般会交替执行,并且它们处于不同空间中。
-
fork()的子进程并不是从头开始,因为在fork()之前,父进程已经为子进程搭建好了运行环境了,所以直接从有效代码处开始。
-
fork底层实现采用了写时拷贝(COW,copy_on_write)技术,父进程和子进程共享页帧而不是复制页帧。然而,只要页帧被共享,它们就不能被修改,即页帧被保护。无论父进程还是子进程何时试图写一个共享的页帧,就产生一个异常,这时内核就把这个页复制到一个新的页帧中并标记为可写。原来的页帧仍然是写保护的:当其他进程试图写入时,内核检查写进程是否是这个页帧的唯一属主,如果是,就把这个页帧标记为对这个进程是可写的。
RDB导致数据丢失问题
由于RDB每次都是进行全量数据的备份,所有这种操作不可能很频繁,不然会影响IO性能,所以RDB会有一个时间间隔,假如这个时间间隔为5分钟,8点整进行了一次RDB,在8点05分点刚要进行RDB的时候,redis意外停止工作(例如停电),这样就会导致8点到8点05分点这个时间段的数据的丢失,如果你能容忍这种数据的丢失的情况,那么就可以使用RDB,一般来说,redis用来做缓存,数据丢失了没关系,可以从数据库中再次加载到redis中。但是如果把redis当成数据库使用的话,那么就不适合使用RDB这种持久化方式了。
RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来调节。
子进程将数据集写入到一个临时RDB文件中,当子进程完成对新RDB文件的写入时,Redis用新RDB文件替换原来的RDB文件,并删除旧的RDB文件。
核心优势
-
高性能:二进制压缩存储,数据恢复速度比AOF快3-5倍
-
紧凑存储:适合灾难恢复,单文件便于传输与备份
-
低资源消耗:后台生成快照时主进程仅短暂阻塞
使用限制
-
数据丢失风险:最后一次快照后的修改可能丢失(典型场景丢失5-15分钟数据)
-
大内存压力:fork子进程时可能产生内存翻倍现象
-
版本兼容性:旧版RDB文件无法直接用于新版Redis
AOF持久化
AOF(Append Of File):记录每次对服务器写的操作,以RESP(REdis Serialization Protocol)协议追加保存每次写的操作到文件末尾。
由于AOF中会存在一些过期的key,以及对一个key的多次写操作都会以redis协议格式追加在文件中尾部,并没有进行压缩,所以会导致AOF文件的体积不断变大,为了解决文件过大的问题,AOF会自动地在后台对文件进行重写。
工作机制
AOF(Append Only File)通过记录写操作命令实现持久化:
-
命令追加:每个写命令以Redis协议格式追加到缓冲区
-
同步策略:根据配置文件中的策略将缓存区中的命令写入磁盘中
-
重写机制:通过
BGREWRITEAOF
消除冗余命令,生成紧凑的新AOF文件
redis.conf
中跟aof相关的核心配置:
# AOF默认关闭
appendonly no
# The name of the append only file (default: "appendonly.aof")
# AOF保存的文件名
appendfilename "appendonly.aof"
# 同步的模式
# always:每收到一个写请求就同步到文件中,这样就不会丢失数据,但是性能低(每次都有磁盘IO)。
# everysec:每秒同步一次,最多丢失一秒的数据,性能介于always和no之间。
# no:no并不是不同步,而是redis只把数据写到操作系统的缓冲区中,具体什么时候写到磁盘依赖操作系统,linux下缓存区大小为512k,缓冲区买了才会刷新到磁盘,性能最好。
# appendfsync always
appendfsync everysec
# appendfsync no
# AOF重写期间不进行同步,避免大量的磁盘IO,影响性能
no-appendfsync-on-rewrite no
# 配置重写触发机制,当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 如果该配置启用,在加载时发现aof尾部不正确时会向客户端写入一个log,但是会继续执行,如果设置为no,发现错误就会停止,必须修复后才能重新加载。
aof-load-truncated yes
跟aof相关的命令:
- bgrewriteaof:手动触发aof的重写操作。
可以通过下面的命令开启aof:
127.0.0.1:6379> config set appendonly yes
OK
127.0.0.1:6379> config set appendfsync everysec
OK
Redis默认在首次开启AOF时可能不会立即生成文件(即使设置了每秒同步写磁盘),需手动执行BGREWRITEAOF命令才会触发aof文件生成。
Redis可以在AOF文件体积变得过大时,重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建新AOF文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析也很轻松,导出AOF文件也非常简单。如果你不小心执行了FLUSHALL命令,但只要AOF文件未被重写,那么只要停止服务器,移除AOF文件末尾的FLUSHALL命令,并重启Redis,就可以将数据集恢复到FLUSHALL执行之前的状态。
对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积,根据所使用的fsync策略,AOF的速度可能会慢于RDB。
核心优势
-
数据完整性:最大程度避免数据丢失(everysec模式最多丢失1秒数据)
-
可读性强:文本格式便于人工审计与修复
-
容灾灵活:支持断点续传式数据恢复
使用限制
-
文件体积大:相同数据集AOF文件通常比RDB大2-5倍
-
恢复速度慢:重放命令方式导致恢复耗时较长
-
写入压力大:高并发场景可能触发磁盘I/O瓶颈
混合持久化模式
Redis 4.0+引入的混合模式结合RDB与AOF的优势。
实现原理
混合模式的前面流程与AOF的流程类似,在重写时是生成RDB快照:
-
存储结构:AOF文件包含RDB头部(全量数据)和增量AOF日志
-
触发条件:AOF重写时自动生成RDB格式的全量数据快照
-
恢复流程:优先加载RDB部分恢复基础数据集,重放后续AOF命令补充增量修改
redis.conf
中跟aof相关的核心配置:
# 启用混合模式,AOF文件的内容前面是否使用RDB
aof-use-rdb-preamble yes
核心优势
-
恢复效率提升:比纯AOF恢复速度提升80%以上
-
数据安全性保留:仍保持AOF的秒级数据持久化能力
-
存储空间优化:RDB压缩减少磁盘占用,AOF增量保持精简
适用场景
-
需要兼顾数据安全与恢复速度的生产环境
-
存在明显冷热数据分层的业务场景
-
需要跨版本迁移数据的特殊场景
方案对比
维度 | RDB | AOF | 混合模式 |
---|---|---|---|
数据安全性 | 分钟级丢失 | 秒级丢失 | 秒级丢失 |
恢复速度 | 最快(GB/秒) | 较慢(命令重放) | 较快(RDB+AOF) |
磁盘占用 | 最小 | 最大 | 中等 |
写入性能影响 | 瞬时压力 | 持续压力 | 均衡压力 |
注:实际部署建议同时开启RDB和AOF,通过
aof-use-rdb-preamble
参数启用混合模式,并定期进行备份验证
启动过程中数据加载流程
Redis在启动时优先加载AOF文件(appendonly.aof)恢复数据,仅当AOF未启用(appendonly no)或AOF文件不存在时,才会加载RDB文件(dump.rdb)。
原因:
-
AOF记录所有写操作命令,通常比RDB快照包含更完整的数据(如两次RDB快照之间的操作)。
-
Redis默认将AOF视为更可靠的持久化方式(除非明确禁用)。