RDB
RDB 概念
Redis 可以通过创建快照来获得存储在内存里面的数据在 某个时间点 上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是 Redis 默认采用的持久化方式,在 redis.conf 配置文件中默认有此下配置:
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发bgsave命令创建快照。RDB 的两种命令方式
Redis 提供了两个命令来生成 RDB 快照文件:
save: 同步保存操作,会阻塞 Redis 主线程;bgsave: fork 出一个子进程,子进程执行,不会阻塞 Redis 主线程,默认选项。fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。
这里说 Redis 主线程而不是主进程的主要是因为 Redis 启动之后主要是通过单线程的方式完成主要的工作。如果你想将其描述为 Redis 主进程,也没毛病。
RDB 持久化过程
开启 RDB 持久化后,Redis 中 x 秒内有 y 个 key 数据被修改后,Redis 就会进行持久化操作。Redis 会使用 fork 创建一个子进程,父子进程共享数据段,父进程继续提供读写服务,该子进程会将数据写入到一个临时文件中,写入后将临时文件替换上次持久化好的文件。
同时,fork 机制下,采用 COW 写时复制技术,从而避免写操作被阻塞。

能否频繁进行 RDB 持久化
bgsave 虽然可以避免子进程持久化的时候阻塞主进程,但是 fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。所以不能太频繁进行 RDB 持久化。
RDB 持久化的优缺点
- 优点:适合备份、容灾恢复;派生子进程完成,父进程不需要 IO 操作,提高了 redis 性能;允许更快地重启数据;节省硬盘空间。
- 缺点:间隔性持久化,可能丢失数据;数据较多,fork 会很耗时。
- 数据不敏感时使用 RDB 持久化。
RDB 持久化命令与相关配置项
save :save 时只管保存,其它不管,全部阻塞。手动保存。不建议。
bgsave :Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。
lastsave :获取最后一次成功执行快照的时间戳 (秒)。
flushall : 也会产生 dump. Rdb 文件,但里面是空的,无意义。
127.0.0.1:6379> save
OK
127.0.0.1:6379> bgsave
Background saving started
127.0.0.1:6379> lastsave
(integer) 1638260083save value1 value2 :每 value1 秒有 value2 个 key 值被修改,则进行保存。注释掉或 save "" 就可以关闭保存。
dir value :配置 rdb 文件保存路径。同样也是 AOF 持久化文件保存路径。
dbfilename value :配置 rdb 文件保存名称
stop-writes-on-bgsave-error :当 Redis 无法写入磁盘的话,直接关掉 Redis 的写操作。推荐 yes。
rdbcompression :是否压缩存储。如果是的话,redis 会采用 LZF 算法进行压缩。推荐 yes。
rdbchecksum :使用 CRC64 算法来进行数据校验,但是会增加约 10%的性能消耗。推荐 yes。
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./使用 RDB 进行恢复
- 先通过
config get dir查询 rdb 文件的目录 - 将
*.rdb的文件拷贝到别的地方
rdb 的恢复
- 关闭 Redis
- 先把备份的文件拷贝到工作目录下
cp dump2.rdb dump.rdb - 启动 Redis, 备份数据会直接加载
动态停止 RDB:redis-cli config set save ""
Save 后给空值,表示禁用保存策略。
AOF
AOF 概念
与快照持久化相比,AOF 持久化的实时性更好。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化(Redis 6.0 之后已经默认是开启了),可以通过 appendonly 参数开启:
appendonly yes
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到 AOF 缓冲区 server.aof_buf 中,然后再写入到 AOF 文件中(此时还在系统内核缓存区未同步到磁盘),最后再根据持久化方式( fsync 策略)的配置来决定何时将系统内核缓存区的数据同步到硬盘中的。
只有同步到磁盘中才算持久化保存了,否则依然存在数据丢失的风险,比如说:系统内核缓存区的数据还未同步,磁盘机器就宕机了,那这部分数据就算丢失了。
AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。
AOF 持久化过程
AOF 是通过日志的形式记录写操作,将写指令追加到日志文件中。具体来说,写指令会追加到 AOF 缓冲区中,根据持久化策略将缓冲区数据写入 AOF 文件中,如果超过重写策略或手动重写时会对文件重写并压缩 AOF 文件容量。
AOF 持久化功能的实现可以简单分为 5 步:
- 命令追加(append):所有的写命令会追加到 AOF 缓冲区中。
- 文件写入(write):将 AOF 缓冲区的数据写入到 AOF 文件中。这一步需要调用
write函数(系统调用),write将数据写入到了系统内核缓冲区之后直接返回了(延迟写)。注意!!!此时并没有同步到磁盘。 - 文件同步(fsync):AOF 缓冲区根据对应的持久化方式(
fsync策略)向硬盘做同步操作。这一步需要调用fsync函数(系统调用),fsync针对单个文件操作,对其进行强制硬盘同步,fsync将阻塞直到写入磁盘完成后返回,保证了数据持久化。 - 文件重写(rewrite):随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 重启加载(load):当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
AOF 持久化方式
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式( fsync 策略),它们分别是:
appendfsync always:主线程调用write执行写操作后,后台线程(aof_fsync线程)立即会调用fsync函数同步 AOF 文件(刷盘),fsync完成后线程返回,这样会严重降低 Redis 的性能(write+fsync)。appendfsync everysec:主线程调用write执行写操作后立即返回,由后台线程(aof_fsync线程)每秒钟调用fsync函数(系统调用)同步一次 AOF 文件(write+fsync,fsync间隔为 1 秒)appendfsync no:主线程调用write执行写操作后立即返回,让操作系统决定何时进行同步,Linux 下一般为 30 秒一次(write但不fsync,fsync的时机由操作系统决定)。
可以看出:这 3 种持久化方式的主要区别在于 fsync 同步 AOF 文件的时机(刷盘)。
为了兼顾数据和写入性能,可以考虑 appendfsync everysec 选项,让 Redis 每秒同步一次 AOF 文件,Redis 性能受到的影响较小。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
从 Redis 7.0.0 开始,Redis 使用了 Multi Part AOF 机制。顾名思义,Multi Part AOF 就是将原来的单个 AOF 文件拆分成多个 AOF 文件。在 Multi Part AOF 中,AOF 文件被分为三种类型,分别为:
- BASE:表示基础 AOF 文件,它一般由子进程通过重写产生,该文件最多只有一个。
- INCR:表示增量 AOF 文件,它一般会在 AOFRW 开始执行时被创建,该文件可能存在多个。
- HISTORY:表示历史 AOF 文件,它由 BASE 和 INCR AOF 变化而来,每次 AOFRW 成功完成时,本次 AOFRW 之前对应的 BASE 和 INCR AOF 都将变为 HISTORY,HISTORY 类型的 AOF 会被 Redis 自动删除。
Multi Part AOF 不是重点,了解即可,详细介绍可以看看阿里开发者的 Redis 7.0 Multi Part AOF 的设计和实现 这篇文章。
相关 issue:Redis 的 AOF 方式 #783。
AOF 为什么是在执行完命令之后记录日志呢
为什么是在执行完命令之后记录日志呢?
- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过):
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
AOF 持久化的优缺点
- 优点
- 支持三种持久化策略;
- 日志易于理解和解析;
- 丢失数据少。
- 缺点
- 同样数据 AOF 文件更大;
- 一般来说持久化比较慢;
- 启动读取数据慢
- AOF 重写机制有阻塞的风险
AOF 重写机制
当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。

由于 AOF 重写会进行大量的写入操作,为了避免对 Redis 正常处理命令请求造成影响,Redis 将 AOF 重写程序放到 子进程 里执行。Redis 还会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。

开启 AOF 重写功能,可以调用 bgrewriteaof 命令手动执行,也可以设置下面两个配置项,让程序自动决定触发时机:
auto-aof-rewrite-min-size:如果 AOF 文件大小小于该值,则不会触发 AOF 重写。默认值为 64 MB;auto-aof-rewrite-percentage:执行 AOF 重写时,当前 AOF 大小(aof_current_size)和上一次重写时 AOF 大小(aof_base_size)的比值。如果当前 AOF 文件大小增加了这个百分比值,将触发 AOF 重写。将此值设置为 0 将禁用自动 AOF 重写。默认值为 100。
Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
Redis 7.0 版本之后,AOF 重写机制得到了优化改进。下面这段内容摘自阿里开发者的从 Redis7.0 发布看 Redis 的过去与未来 这篇文章。
AOF 重写期间的增量数据如何处理一直是个问题,在过去写期间的增量数据需要在内存中保留,写结束后再把这部分增量数据写入新的 AOF 文件中以保证数据完整性。可以看出来 AOF 写会额外消耗内存和磁盘 IO,这也是 Redis AOF 写的痛点,虽然之前也进行过多次改进但是资源消耗的本质问题一直没有解决。
阿里云的 Redis 企业版在最初也遇到了这个问题,在内部经过多次迭代开发,实现了 Multi-part AOF 机制来解决,同时也贡献给了社区并随此次 7.0 发布。具体方法是采用 base(全量数据)+inc(增量数据)独立文件存储的方式,彻底解决内存和 IO 资源的浪费,同时也支持对历史 AOF 文件的保存管理,结合 AOF 文件中的时间信息还可以实现 PITR 按时间点恢复(阿里云企业版 Tair 已支持),这进一步增强了 Redis 的数据可靠性,满足用户数据回档等需求。
相关 issue:Redis AOF 重写描述不准确 #1439。
AOF 相关配置项
以下是 Redis 配置文件中与 AOF 相关的几个重要配置项:
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mbappendonly yes:启用 AOF 持久化。appendfilename "appendonly.aof":指定 AOF 文件的名称。appendfsync everysec:每秒同步一次 AOF 文件到磁盘。no-appendfsync-on-rewrite no:在重写 AOF 文件时是否禁用appendfsync。yes , 不写入 aof 文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)。no, 还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)auto-aof-rewrite-percentage 100:当 AOF 文件大小增长超过当前大小的 100% 时自动触发BGREWRITEAOF(文件是原来重写后文件的 2 倍时触发)。设置为 0,则禁用重写。auto-aof-rewrite-min-size 64mb:只有当 AOF 文件大小至少为 64MB 时才触发自动重写。默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发。aof-load-truncated yes:AOF 文件出错时,是否加载截断的文件
AOF 校验机制
AOF 校验机制是 Redis 在启动时对 AOF 文件进行检查,以判断文件是否完整,是否有损坏或者丢失的数据。这个机制的原理其实非常简单,就是通过使用一种叫做 校验和(checksum) 的数字来验证 AOF 文件。这个校验和是通过对整个 AOF 文件内容进行 CRC64 算法计算得出的数字。如果文件内容发生了变化,那么校验和也会随之改变。因此,Redis 在启动时会比较计算出的校验和与文件末尾保存的校验和(计算的时候会把最后一行保存校验和的内容给忽略点),从而判断 AOF 文件是否完整。如果发现文件有问题,Redis 就会拒绝启动并提供相应的错误信息。AOF 校验机制十分简单有效,可以提高 Redis 数据的可靠性。
类似地,RDB 文件也有类似的校验机制来保证 RDB 文件的正确性,这里就不重复进行介绍了。
使用 AOF 进行启动/修复/恢复
AOF 的备份机制和性能虽然和 RDB 不同, 但是备份和恢复的操作同 RDB 一样,都是拷贝备份文件,需要恢复时再拷贝到 Redis 工作目录下,启动系统即加载。
正常恢复
- 修改默认的
appendonly no,改为 yes - 将有数据的 aof 文件复制一份保存到对应目录
- 重启 redis 然后重新加载
异常恢复
- 修改默认的
appendonly no,改为 yes - 如遇到 AOF 文件损坏,通过
/usr/local/bin/redis-check-aof --fix appendonly.aof进行恢复 - 恢复:重启 redis,然后重新加载
AOF 重写机制的阻塞风险
这里有两个风险。
风险一:Redis 主线程 fork 创建 bgrewriteaof 子进程时,内核需要创建用于管理子进程的相关数据结构,这些数据结构在操作系统中通常叫作进程控制块(Process Control Block,简称为 PCB)。内核要把主线程的 PCB 内容拷贝给子进程。这个创建和拷贝过程由内核执行,可能会短暂地阻塞主线程,尤其是在内存较大的情况下。而且,在拷贝过程中,子进程要拷贝父进程的页表,这个过程的耗时和 Redis 实例的内存大小有关。如果 Redis 实例内存大,页表就会大,fork 执行时间就会长,这就会给主线程带来阻塞风险。
风险二:bgrewriteaof 子进程会和主线程共享内存。当主线程收到新写或修改的操作时,主线程会申请新的内存空间,用来保存新写或修改的数据。也称为写时复制 copy-on-write 技术。如果操作的是 bigkey,也就是数据量大的集合类型数据,那么,主线程会因为申请大空间而面临阻塞风险。因为操作系统在分配内存空间时,有查找和锁的开销,这就会导致阻塞。
AOF 和 RDB 开启问题
AOF 默认不开启;两者同时开启时,Redis 使用 AOF 持久化方法。
如果做内存型数据库,推荐两者都开启,同时定时进行 bgsave 备份。
Redis4.0 开始支持混合持久化 (默认关闭)。AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。可以通过配置项 aof-use-rdb-preamble 开启。
场景题
某配置机器进行 RDB 持久化是否有风险
我曾碰到过这么一个场景:我们使用一个 2 核 CPU、4GB 内存、500GB 磁盘的云主机运行 Redis,Redis 数据库的数据量大小差不多是 2GB,我们使用了 RDB 做持久化保证。当时 Redis 的运行负载以修改操作为主,写读比例差不多在 8:2 左右,也就是说,如果有 100 个请求,80 个请求执行的是修改操作。你觉得,在这个场景下,用 RDB 做持久化有什么风险吗?你能帮着一起分析分析吗?
内存不足的风险:Redis fork 一个 bgsave 子进程进行 RDB 写入,如果主线程再接收到写操作,就会采用写时复制。写时复制需要给写操作的数据分配新的内存空间。本问题中写的比例为 80%,那么,在持久化过程中,为了保存 80% 写操作涉及的数据,写时复制机制会在实例内存中,为这些数据再分配新内存空间,分配的内存量相当于整个实例数据量的 80%,大约是 1.6GB,这样一来,整个系统内存的使用量就接近饱和了。此时,如果实例还有大量的新 key 写入或 key 修改,云主机内存很快就会被吃光。如果云主机开启了 Swap 机制,就会有一部分数据被换到磁盘上,当访问磁盘上的这部分数据时,性能会急剧下降。如果云主机没有开启 Swap,会直接触发 OOM,整个 Redis 实例会面临被系统 kill 掉的风险。
主线程和子进程竞争使用 CPU 的风险:生成 RDB 的子进程需要 CPU 核运行,主线程本身也需要 CPU 核运行,而且,如果 Redis 还启用了后台线程,此时,主线程、子进程和后台线程都会竞争 CPU 资源。由于云主机只有 2 核 CPU,这就会影响到主线程处理请求的速度。