Redis 的持久化 RDB和 AOF
1. 简介
Redis 是一个快速的 key-value 形式的 内存数据库。同时Redis支持RDB
和AOF
两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。
2. 持久化
2.1 RDB
RDB持久化是把当前进程数据生成快照
保存到硬盘的过程,触发RDB持久化过程分为手动触发
和自动触发
。
2.1.1 如何触发
手动触发
手动触发分为两个命令 save 和 bgsave 。
- save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成
长时间阻塞
,线上环境不建议使用 - bgsave命令:Redis进程执行
fork
操作创建子进程,RDB持久化过程由子进程
负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。
因为bgsave命令是针对 save命令做了优化的,所以Redis现在内部RDB的操作都是使用了bgsave的方式。
自动触发
Redis 内部会进行自动触发RDB操作。
- 1)使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
- 2)如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文319件并发送给从节点
- 3)执行debug reload命令重新加载Redis时,也会自动触发save操作。
- 4)默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。
在redis.conf 中有如下配置:
**
- save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave(这个命令下面会介绍,手动触发RDB持久化的命令)
默认如下配置:
save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存 save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存 save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存
当然如果你只是用Redis的缓存功能,不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。可以直接一个空字符串来实现停用:save ""
- stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了
- rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。
- rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
- dbfilename :设置快照的文件名,默认是 dump.rdb
- dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在同一目录。
也就是说通过在配置文件中配置的 save 方式,当实际操作满足该配置形式时就会进行 RDB 持久化,将当前的内存快照保存在 dir 配置的目录中,文件名由配置的 dbfilename 决定。
2.1.2 处理流程
下图是 bgsave 的处理流程图。
- 执行
bgsave
命令 ,Redis 的主进程进行处理,判断当前是否存在子进程正在进行 RDB/AOF 操作,直接返回。 -
父进程
执行fork
操作 创建子进程,fork 过程中父进程会阻塞,fork属于操作系统中的重操作。通过info stats命令查看latest_fork_usec
选项,可以获取最近一个fork操作的耗 时,单位为微秒。 - 父进程 fork完之后 可以继续处理其他命令。
- 子进程会按照一定规则 生成 RDB文件 ,并对
原有文件
进行原子替换
。执行lastsave
命令可以获取最后一次生成RDB的 时间,对应info统计的rdb_last_save_time
选项。 - 进程发送信号给父进程表示完成,父进程更新统计信息,具体见
info Persistence
下的rdb_*
相关选项。
> 生成目录:RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。可以通过执行config set dir{newDir}和config set dbfilename{newFileName}运行期动态执行,当下次运行时RDB文件会保存到 新目录> 。
> 压缩:Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression{yes|no}动态修改。压缩会消耗CPU,但是会大幅降低 RDB文件体积。
2.1.3 RDB 的优缺点
RDB 的优点
- RDB是一个按照指定规则压缩的
紧凑二进制文件
。代表Redis在某个时间点的数据快照,非常适用于备份,全量复制等。 - RDB 文件的加载远远快于 AOF文件。
RDB的缺点
- RDB 的
执行成本比较高,
因为 fork属于重量级操作。RDB没法做到实时持久化/秒级持久化.
- RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在
老版本``Redis``服务无法兼容新版``RDB``格式
的问题。
针对这个问题,redis 还有另外一种 持久化方法 AOF。
2.2 AOF
AOF
(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性
,目前已经是Redis持久化的主流方式
。
2.2.1 触发方式
自动触发
在redis.conf 配置中可以看到:
- appendonly:默认值为no,也就是说redis
默认使用的是rdb方式持久化
,如果想要开启 AOF 持久化方式,需要将 appendonly 修改为 yes。 - appendfilename :aof文件名,默认是"appendonly.aof"
- **appendfsync:**aof持久化策略的配置。通常有三种方式。
no 表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快,但是不太安全;always 表示每次写入都执行fsync,以保证数据同步到磁盘,效率很低;everysec 表示每秒执行一次fsync,可能会导致丢失这1s数据。通常选择 everysec ,兼顾安全性和效率
- no-appendfsync-on-rewrite:在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。 设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。默认值为no。
- auto-aof-rewrite-percentage:
默认值为100
。aof自动重写配置,当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候,Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。 - auto-aof-rewrite-min-size:
64mb
。设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写。 - aof-load-truncated:aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项,出现这种现象 redis宕机或者异常终止不会造成尾部不完整现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。默认值为 yes。
2.2.2 处理流程
AOF的工作流程操作:命令写入 (append)、文件同步(sync)、文件重写(rewrite)、重启加载 (load),如图。
- 所有的写入命令会追加到
aof_buf
(缓冲区)中。 - AOF缓冲区根据对应的策略向硬盘做同步操作。
- 随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
- 当Redis服务器重启时,可以加载AOF文件进行数据恢复。
> AOF 写入的都是文本命令。例如 set key value 等。
1)AOF为什么直接采用文本协议格式?可能的理由如下:
·文本协议具有很好的兼容性。
·开启AOF后,所有写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销。
·文本协议具有可读性,方便直接修改和处理。
2)AOF为什么把命令追加到aof_buf中?
Redis使用单线程响应命令,如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负
载。先写入缓冲区aof_buf中,还有另一个好处,Redis可以提供多种缓冲区 同步硬盘的策略,在性能和安全性方面做出平衡。
2.2.3 AOF 重写
为什么需要重写?
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
重写的优点
- 进程内已经超时的数据不再写入文件。
-
旧的``AOF``文件含有无效命令
,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保 留最终数据的写入命令。 -
多条写命令可以合并为一个
,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多条。 - 更小的AOF文件可以被
更快的加载
。
重写的触发
AOF重写过程可以手动触发和自动触发
:
- 手动触发:直接调用
bgrewriteaof
命令。 - 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数确定自动触发时机。
- auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认 为64MB。
- auto-aof-rewrite-percentage:代表当前AOF文件空间 (aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的比值 默认为 100,为2倍。
- 自动触发时机=aof_current_size>auto-aof-rewrite-min-size&&(aof_current_size aof_base_size)/aof_base_size>=auto-aof-rewrite- percentage .
aof文件大于64MB并且为上次重写后AOF文件的2倍。
其中aof_current_size
和aof_base_size
可以在info Persistence
统计信息中查看。
重写的工作流程
1)执行AOF重写请求。如果当前进程正在执行AOF重写,请求不执行并返回如下响应:ERR Background append only file rewriting already in progress 如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再 执 行,返回如下响应:Background append only file rewriting scheduled
2)父进程执行fork创建子进程,开销等同于bgsave过程。
3.1)主进程fork操作完成后,继续响应其他命令。所有修改命令依然写 入AOF缓冲区并根据appendfsync策略 同步到硬盘,保证原有AOF机制正确 性。
3.2)由于fork操作运用写时复制技术
,子进程只能共享fork操作时的内 存数据。由于父进程依然响应命令, Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。
4)子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每 次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为 32MB,防止单次刷盘数据过多造成硬盘阻塞。
5.1)新AOF文件写入完成后,子进程发送信号给父进程,父进程更新 统计信息,具体见info persistence下的aof_*相关统计。
5.2)父进程把AOF重写缓冲区的数据写入到新的AOF文件。
5.3)使用新AOF文件替换老文件,完成AOF重写。
2.2.4 AOF 的优缺点
AOF的优点
- 通过配置可以达到 实时持久化/秒级持久化 (会受限于磁盘性能)
- AOF文件写入的都是文本命令,可以手动修改,可读性强,人工可干预。
AOF的缺点
- AOF文件的体积要比RDB文件大
- 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB 比 AOF 具好更好的性能保证
2.3 RDB-AOF混合
这里补充一个知识点,在Redis4.0
之后,又新增了RDB-AOF
混合持久化方式。
这种方式结合了RDB和AOF的优点,既能快速加载又能避免丢失过多的数据。
具体配置为:aof-use-rdb-preamble
设置为yes表示开启,设置为no表示禁用。
当开启混合持久化时,主进程先fork出子进程将现有内存副本全量以RDB方式写入aof文件中,然后将缓冲区中的增量命令以AOF方式写入aof文件中,保证持久化的实时性,写入完成后通知主进程更新相关信息,并将新的含有 RDB和AOF两种格式的aof文件替换旧的aof文件。
简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。
这种方式优点我们很好理解,缺点就是不能兼容Redis4.0之前版本的备份文件了。
2.3 启动加载流程
总结
- Redis提供了两种持久化方式:
RDB``和``AOF
。 - RDB使用一次性生成内存快照的方式,产生的
文件紧凑压缩比更高
,因此读取RDB恢复速度更快
。由于每次生成RDB开销较大
,无法做到实时持久化,一般用于数据冷备和复制传输
。 - save命令会阻塞主线程不建议使用,bgsave命令通过fork操作创建子进程生成RDB避免阻塞。
- AOF通过追加写命令到文件实现持久化,通过appendfsync参数可以
控制实时``/``秒级持久化
。因为需要不断追加写命令,所以AOF文件体积逐渐变大,需要定期执行重写
操作来降低文件体积
。 - AOF重写可以通过auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数控制自动触发,也可以使用bgrewriteaof命令手动触发。
- 子进程执行期间使用copy-on-write机制与父进程共享内存,避免内存消耗翻倍。AOF重写期间还需要
维护重写缓冲区
,保存新的写入命令避免 数据丢失
。 - 持久化阻塞主线程场景有:fork阻塞和AOF追加阻塞。fork阻塞时间跟内存量和系统有关,AOF追加阻塞说明硬盘资源紧张。
- 单机下部署多个实例时,为了防止出现多个子进程执行重写操作,建议做隔离控制,避免CPU和IO资源竞争。
参考:《redis开发与运维》