RDB、AOF 和混合持久化机制
前言
我们知道 Redis 是内存数据库,它把数据都存储在了内存中,如果 Redis 服务器出现了意外,比如宕机、断电等情况,那么内存中的数据就会全部丢失。所以必须有一种机制可以把内存中的数据保存到磁盘里面,为了解决这个问题,Redis 提供了 RDB 和 AOF 两种持久化机制,这也是 Redis 的重要特性之一。
1. RDB(默认开启)
1.1 简介
RDB 全称 Redis Database Backup file,也被称为 Redis 数据快照。
RDB 持久化功能生成的 RDB 文件是一个经过压缩的二进制文件(默认为 dump.rdb,也可以在 redis.conf 文件中修改),默认保存在当前的运行目录(也可以在 redis.conf 文件中修改),简单来说就是把内存中的数据都记录在磁盘上,当 Redis 出现意外,可以通过 Redis 重启加载该文件来恢复数据。
# redis.conf
# 要备份数据库的文件名
dbfilename dump.rdb
# 工作目录。
# 数据库将会写在这个目录中,使用上面通过 ‘dbfilename’ 配置指令指定的文件名。同时,追加日志文件也 # 会在这个目录中创建。
# 请注意,这里需要指定一个目录,而不是一个文件名。
dir ./ #RDB和AOF目录
RDB 持久化既可以手动执行,也可以根据服务器配置选项定期自动执行。
1.2 RedisClient 手动执行(SAVE、BGSAVE)
可以用 SAVE 和 BGSAVE 命令来手动生成 RDB 文件,但这两者有所不同。
- SAVE 命令:
SAVE 900 1 # SAVE <指定时间间隔> <执行指定次数更新操作>
SAVE 命令会阻塞 Redis 进程,直到 RDB 文件创建完毕为止,在这之前,服务器不能处理任何命令请求。
- BGSAVE 命令:
BGSAVE
BGSAVE 命令会创建一个子进程来创建 RDB 文件,所以在创建过程中,Redis 服务器仍然可以处理客户端的命令请求。
1.3 RedisServer 自动执行(save 配置)
Redis 允许通过配置 redis.conf 文件中的 save 配置,让服务器每隔一段时间自动执行一次 BGSAVE 命令,可以设置多个保存条件,比如以下 3 个默认条件(注释已翻译为中文):
# redis.conf
# 将数据库保存到磁盘上:
# save <秒数> <更改数>
# 如果满足给定的秒数和对数据库的写操作次数,则保存数据库。
# 在下面的示例中,行为将是:
# 当至少有1个键发生更改时,每900秒(15分钟)保存一次
# 当至少有10个键发生更改时,每300秒(5分钟)保存一次
# 当至少有10000个键发生更改时,每60秒保存一次
# 注意:您可以通过注释掉所有的 "save" 行来完全禁用保存功能。
# 也可以通过添加一个只有一个空字符串参数的保存指令来删除所有先前配置的保存点,如下面的示例:
# save ""
save 900 1 #在900秒(15分钟)之内,对数据库进行了至少1次修改,则执行一次BGSAVE
save 300 10 #在300秒(5分钟)之内,对数据库进行了至少10次修改,则执行一次BGSAVE
save 60 10000 #在60秒之内,对数据库进行了至少10000次修改,则执行一次BGSAVE
1.4 RDB 文件载入(恢复数据)
服务器在恢复数据库数据(载入 RDB 文件)时,会一直处于阻塞状态,直到载入完成为止。
1.5 实现原理
当 Redis 服务启动时,用户可以通过指定配置文件或者传入启动参数的方式设置 save 选项,如果没有主动设置,服务器就会使用 redis.conf 文件中默认的条件(上述 3 个条件)。
接着,服务器会根据 save 的选项所设置的保存条件,设置服务器状态 redisServer 结构的 saveparams 属性,除此之外,还有一个 dirty 计数器及 lastsave 属性:
struct redisServer{
//....
//记录了保存条件的数组
struct saveparam *saveparams;
//修改计数器
long long dirty;
//上一次执行保存的时间
time_t lastsave;
//.....
};
- saveparams 属性:是一个数组,每个 saveparams 结构都保存了一个 save 选项设置的保存条件:
struct saveparam{
//秒数
time_t seconds;
//修改数
int changes;
};
dirty 属性:记录上一次成功成功执行 SAVE 命令或 BGSAVE 命令之后,服务器对数据库(全部数据库)进行了多少次修改(包括写入、删除、更新等操作)。
lastsave 属性:是一个 UNIX 时间戳,记录服务器上一次成功执行 SAVE 或 BGSAVE 命令的时间。
那么它是如何被调用的呢?
Redis 的服务器有一个周期性操作函数 serverCron,它每隔 100ms 就会执行一次,该函数用于对正在运行的服务器进行维护,其中的一项工作就是检查 save 选项所设置的保存条件是否已满足,如果满足就执行 BGSAVE 命令。
2. AOF(默认关闭)
2.1 简介
AOF 全称 Append Only File,是 Redis 另外一种持久化机制,默认不开启(可在 redis.conf 文件中配置),它的目的是为了解决生成 RDB 文件后数据不能实时一致的问题,所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis 重启会根据日志文件的内容将写命令从前到后执行一遍来恢复数据。
# redis.conf
# 默认情况下,Redis会异步将数据集转储到磁盘上。这种模式在许多应用中已经足够好了,但是如果 # Redis进程出现问题或者停电,可能会导致几分钟的写入丢失(取决于配置的保存点)。
# “追加模式文件”(Append Only File)是一种提供更好耐久性的备份模式。例如,在使用默认的数据
# 同步策略(在配置文件中稍后会提到)时,Redis在发生重大事件(如服务器停电)时可能会丢失一秒
# 钟的写入,或者如果Redis进程本身出现问题但操作系统仍在正常运行时,可能会丢失单个写入。
# 可以同时启用AOF持久化和RDB持久化,没有任何问题。如果在启动时启用AOF,Redis将加载AOF文件,
# 该文件具有更好的耐久性保证。
# 请访问 http://redis.io/topics/persistence 了解更多信息。
appendonly no #默认为no,开启改为yes
# 追加模式文件的名称是"appendonly.aof"(默认值)。
appendfilename "appendonly.aof"
2.2 AOF 持久化的实现
AOF 持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤:
命令追加: 服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。aof 缓冲区是 redisServer 结构体维护的一个 SDS 结构的属性。
文件写入: 将 aof 缓冲区的数据写入到 AOF 文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区 page cache(操作系统),等待内核将数据写入硬盘;具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。
文件同步: 这个过程是将内核缓冲区中的数据写入到硬盘中的 AOF 文件中。如果由内核决定将内核数据写入硬盘的话,如果服务器宕机,那么就会丢失数据。为了解决这个问题,系统提供了 fync 和 fdatasync 两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘,以及三种策略:
- always:同步写回,每个写命令执行完立刻同步地将日志写回磁盘。(性能最差,最多丢失一个写指令的数据)
- everysec(默认):每秒执行一次。(是性能和数据安全性的折中方案,最多也就丢一秒的数据)
- no:根据操作系统和资源的情况,一定时间执行一次,时间不确定。(性能最好,可能会丢失上次同步 AOF 文件之后的所有写命令数据)
# fsync() 调用告诉操作系统实际上将数据写入磁盘,而不是等待输出缓冲区中的更多数据。有些操作系统会真的刷新数据到磁盘,而其他操作系统则会尽快尝试刷新数据。
# Redis 支持三种不同的模式:
# no: 不进行 fsync,只让操作系统在需要时刷新数据。更快。
# always: 每次写入追加日志后进行 fsync。较慢,但更安全。
# everysec: 每秒只进行一次 fsync。取折中。
# 默认是 "everysec",因为这通常是速度和数据安全之间的合适折衷。您需要理解是否可以将其放松到 "no",这允许操作系统在适当时刷新输出缓冲区,以获得更好的性能(但如果您可以接受某些数据丢失的想法,请考虑默认的持久化模式,即快照),或者相反,使用 "always",虽然非常慢,但比 "everysec" 稍微安全一些。
# 有关更多详细信息,请查阅以下文章:
# http://antirez.com/post/redis-persistence-demystified.html
# 如果不确定,可以使用 "everysec"。
# appendfsync always #同步
appendfsync everysec #每秒
# appendfsync no #由操作系统决定
2.3 AOF 文件载入(恢复数据)
因为 AOF 文件里包含了所有写命令,所以服务器只要读入并重新执行一遍 AOF 文件里面的命令,就可以还原服务器关闭之前的数据,详细步骤如下:
- 创建一个不带网络连接的伪客户端(fake client),因为 Redis 的命令只能在客户端上下文中执行,而载入 AOF 文件时所使用的命令直接来源于 AOF 文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行 AOF 文件保存的写命令,效果和带网络连接的客户端一样。
- 从 AOF 文件中分析并读取一条写命令。
- 使用伪客户端执行被读出的写命令。
- 重复执行 2 和 3 步骤,直到 AOF 文件中的所有写命令都被处理完毕为止。
2.4 AOF 重写
因为 AOF 是通过保存被执行的写命令来持久化的,所以随着 Redis 的长时间运行,AOF 的体积会越来越大,AOF 文件过大很可能对 Redis 服务器、甚至整个宿主机造成影响,所以 Redis 提供了重写的策略来应对这个问题。
- AOF 重写配置
#redis.conf
# 自动重写只追加文件。
# 当 AOF 日志文件的大小增长到指定的百分比时,Redis 可以通过隐式调用 BGREWRITEAOF 来自动重写日志文件。
# 工作原理如下:Redis 在最新的重写之后记住 AOF 文件的大小(如果自重启以来没有发生重写,则使用启动时的 AOF 大小)。
# 将此基准大小与当前大小进行比较。如果当前大小大于指定的百分比,则会触发重写。此外,您需要指定重写 # AOF 文件的最小大小,这对于避免即使达到增加百分比,但仍非常小的情况下重写 AOF 文件非常有用。
# 指定百分之零的百分比以禁用自动 AOF 重写功能。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #表示触发AOF重写的最小文件体积,大于或等于64MB自动触发。
AOF 重写流程
Redis 会启动一个 AOF 重写子进程,负责执行 AOF 重写操作。同时,Redis 会继续处理新的写入命令,并将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。
- AOF 缓冲区:无论重不重写都有这个缓冲区,AOF 日志写入是 AOF 缓冲区->AOF 文件。
- AOF 重写缓冲区:AOF 重写子进程启动后开始使用。
子进程会按照一定的规则,读取当前数据库的键值对,并将其转化为合适的命令格式,保存到 AOF 重写缓冲区中。
子进程在遍历完整个数据库之后,就完成了重写工作。
3. RDB VS AOF
RDB 和 AOF 在数据可靠性、性能、存储空间、使用场景等方面都有不同的优缺点,具体可以根据实际业务需求和硬件条件选择合适的持久化机制,或者同时使用两种持久化机制来实现更高的数据可靠性:
数据可靠性:
RDB:可能会丢失最后一次快照之后的数据。如果 RDB 持久化过程中服务器宕机了,那么就会丢失这一次的数据。
AOF:可能会丢失最后一次(或一秒)写操作的数据。如果 Redis 刚刚执行完一个写命令,还没来得及写 AOF 文件就宕机了,那么就会丢失这一条数据【当然也得看它配置的策略,如果配置的是 always(同步),那就丢一条,配置的 everysec(每秒)那就会丢 1 秒的数据】,但它也比 RDB 更加靠谱一些。
性能:
RDB:备份和数据恢复比较快,适合做数据恢复。RDB 存的是原生数据,所以直接加载到内存中即可。
AOF:写性能较高【RDB 是对整个物理中的数据的快照,AOF 则仅仅是记录每次写命令】,但数据恢复速度相对较慢【AOF 需要对命令从头到尾再执行一次】。
存储空间:
RDB:二进制文件,体积较小。
AOF:文本文件,体积较大。
使用场景:
RDB:适用于需要定期备份、大规模数据恢复、恢复速度要求比较快的场景。
AOF:适用于对数据完整性要求较高、数据存档的场景。
注意
既然都有各自的优缺点,那么它俩同时开启会怎样?
Redis 4.0 之前,如果两种方式同时开启,dump.rdb 和 appendonly.aof 文件都会生成,但在恢复数据时,会优先用 appendonly.aof 来恢复(比较完整),但 AOF 恢复数据相对较慢,如果 Redis 实例比较大的情况下,启动要花费很长时间。但再 Redis 4.0 后就优化了这个问题,这也是下面即将介绍的内容。
4. RDB 和 AOF 混合持久化
RDB 和 AOF 混合持久化
Redis 4.0 为了解决上面的问题,带来了一个新的持久化选项——混合持久化。在开启混合持久化的情况下,AOF 重写时会把 Redis 的持久化数据,以 RDB 的格式写入到 AOF 文件的开头,之后的数据再以 AOF 的格式追加到文件的末尾。
#redis.conf
# 当重写 AOF 文件时,Redis能够在 AOF 文件中使用 RDB 前导以实现更快的重写和恢复。当开启此选项时,
# 重写后的 AOF 文件由两个不同的部分组成:
# [RDB 文件][AOF 尾部]
# 当加载 Redis 时,它会识别 AOF 文件以"REDIS"字符串开头,并加载带有前缀的 RDB 文件,然后继续加载 # AOF 的尾部。
aof-use-rdb-preamble yes
优缺点:
总结
优点:混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 格式,可以使 Redis 启动的更快,同时结合 AOF 的优点,又降低了大量数据丢失的风险。
缺点:在 AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;如果开始混合持久化,那么混合持久化的 AOF 是不能在旧版本中用的,不能向下兼容。
