Redis持久化有两种方式:RDB(Redis DataBase)和AOF(Append Only File)
RDB
RDB 是 Redis 默认的持久化方式(AOF默认是关闭的),它将 Redis 在内存中的数据写入到硬盘中,生成一个快照文件。
快照文件是一个二进制文件,包含了 Redis 在某个时间点内的所有数据。
RDB的优点是快速、简单,适用于大规模数据备份和恢复。
但是,RDB也有缺点,例如数据可能会丢失,因为 Redis 只会在指定的时间点生成快照文件。如果在快照文件生成之后,但在下一次快照文件生成之前服务器宕机,那么这期间的数据就会丢失。
RDB 持久化触发有两种方式:自动 和 手动
手动
- save:save 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
- bgsave:bgsave 命令会 fork 一个子进程(注意是子进程,不是子线程)在后台生成快照文件,不会阻塞 Redis 服务器,服务器进程(父进程)可以继续处理命令请求
自动
自动方式是指通过服务器配置文件的 save 选项,来让 Redis 每隔一段时间自动执行 bgsave ,本质上还是通过 bgsave 命令去实现。
配置文件的 save 选项允许配置多个条件,只要其中任何一个条件满足,就会触发 bgsave。
即:"N 秒内数据集至少有 M 个改动" 这一条件被满足时。
举个例子,如果我们向服务器提供以下配置:
save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任意一个,bgsave 命令就会被执行:
- 服务器在 900秒 之内,对数据库进行了至少 1次 修改。
- 服务器在 300秒 之内,对数据库进行了至少 10次 修改。
- 服务器在 60秒 之内,对数据库进行了至少 10000次 修改。
知道上面这些之后我们还要解决两个问题:
- save 选项配置,Redis是通过什么来判断的?
- Redis 判断的时机是什么时候?
问题一:save 选项配置,Redis是通过什么来判断的?
Redis 服务器内部维护了一个计数器:dirty 和一个时间戳:lastsave。
- dirty计数器:dirty计数器记录距离上一次成功执行 save 命令或者 bgsave 命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作),命令修改了多少次数据库,dirty计数器的值就增加多少。
- lastsave时间戳:lastsave时间戳则记录了服务器上一次成功执行 save 命令或者 bgsave 命令的时间。
举个例子,某一时刻,dirty 计数器和 lastsave 时间戳的值如下:
dirty:200
lastsave:1703952000000
表示服务器在上次保存之后(2023-12-31 00:00:00),对数据库状态共进行了 200 次修改。
通过这种方式来判断条件是否满足。
问题二:Redis 判断的时机是什么时候?
Redis通过周期性操作函数 serverCron 默认每隔100毫秒就会执行一次检查,来判断 save 选项所设置的保存条件是否已经满足。
serverCron 会遍历所有保存条件,只要有任意一个条件被满足,就执行 bgsave 命令。
AOF
AOF 持久化是按照 Redis 的写命令顺序将写命令追加到磁盘文件的末尾,是一种基于日志的持久化方式,它保存了 Redis 服务器所有写入操作的日志记录。
AOF 的核心思想是将 Redis 服务器执行的所有写命令追加到一个文件中。当Redis服务器重新启动时,可以通过重新执行 AOF 中的命令来恢复服务器的状态。
AOF 的写入与同步
当启用 AOF 时,Redis 发生写命令时其实并不是直接写入到AOF 文件,而是将写命令追加到AOF缓冲区的末尾,之后 AOF缓存区再同步至 AOF文件中。
这行为其实不难理解,Redis 写入命令十分频繁,而 AOF 文件又位于磁盘上,如果每次发生写命令就要操作一次磁盘,性能就会大打折扣。
而 AOF 缓存区同步至 AOF 文件,这一过程由名为 flushAppendonlyFile
的函数完成。
而 flushAppendOnlyFile
函数的行为由服务器配置文件的 appendfsync
选项来决定,该参数有以下三个选项:
- always:每次发生写命令,都同步到 AOF 文件,是最安全的选项。
- everysec:每秒钟同步写入一次到 AOF 文件,在性能和安全之间做了一个平衡。
- no:不主动写入 AOF 文件,何时同步由操作系统来决定。
默认情况下,Redis的 appendfsync
参数为 everysec
。
AOF 重写
上面我们讲了 AOF 是通过追加命令的方式去记录数据库状态的,那么当随着服务器运行时间的流逝,AOF 文件可能会越来越大,达到几G甚至几十个G。
过大的 AOF 文件会对 Redis 服务器甚至宿主机造成影响,并且 AOF 越大,使用 AOF 来进行数据恢复所需的时间也就越多。
为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写(rewrite)机制。
Redis的 AOF 重写机制指的是将 AOF 文件中的冗余命令删除,以减小 AOF 文件的大小并提高读写性能的过程。
通过该功能,Redis 服务器可以创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个 AOF 文件所保存的数据库状态相同,但新 AOF 文件不会包含任何浪费空间的冗余命令,所以新 AOF 文件的体积通常会比旧 AOF 文件的体积要小得多。
AOF重写的实现
虽然叫做AOF重写,但实际上,AOF 文件重写并不需要对现有的AOF 文件进行任何读取、分析或者写入操作。
AOF重写是通过读取服务器当前的数据库状态来实现的。
我举个例子大家就明白了,假设我对 Redis 执行了下面六条命令:
rpush list "A"
rpush list "B"
rpush list "C"
rpush list "D"
rpush list "E"
rpush list "F"
那么服务器为了保存当前 list键 的状态,会在AOF文件中写入上述六条命令。
而我现在要对 AOF 进行重写的话,其实最高效最简单的方式不是挨个读取和分析现有AOF文件中的这六条命令。
而是直接从数据库中读取键 list 的值,然后用一条命令:rpush list "A" "B" "C" "D" "E" "F"
,可以直接代替原 AOF 文件中的六条命令。
AOF 重写面临的问题
子进程在AOF重写期间,父进程还是在继续接收和处理命令的。
那么就存在一个问题:新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。
AOF重写缓存区
为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区。
AOF重写缓存区在AOF重写时开始启用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
由了AOF重写缓存区的存在,当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接到该信号之后,会调用处理函数,将AOF重写缓冲区中的所有内容写入到新AOF文件中(就是重写后的文件),这样重写后数据库状态就和服务器当前的数据库状态一致了。
混合持久化
这种持久化能够通过 AOF 重写操作创建出一个同时包含 RDB 数据和 AOF 数据的 AOF 文件, 其中 RDB 数据位于 AOF 文件的开头, 它们储存了服务器开始执行重写操作时的数据库状态。至于那些在重写操作执行之后执行的 Redis 命令, 则会继续以 AOF 格式追加到 AOF 文件的末尾, 也即是 RDB 数据之后。