Redis 的持久化机制有哪些?
Redis 的持久化机制有哪些?
回答重点
Redis 提供两种主要的持久化机制:
RDB(Redis Database)快照:
- RDB 是通过生成某一时刻的数据快照来实现持久化的,可以在特定时间间隔内保存数据的快照。
- 适合灾难恢复和备份,能生成紧凑的二进制文件,但可能会在崩溃时丢失最后一次快照之后的数据。
AOF(Append Only File)日志:
- AOF 通过将每个写操作追加到日志文件中实现持久化,支持将所有写操作记录下来以便恢复。
- 数据恢复更为精确,但文件体积较大,重写时可能会消耗更多资源。
Redis 4.0 新增了 RDB 和 AOF 的混合持久化机制。
扩展知识
RDB 持久化详解
RDB 持久化通过创建快照来获取内存某个时间点上的副本,利用快照可以进行方便地进行主从复制,默认快照文件为 dump.rdb。
redis.conf 文件可以配置在 x 秒内如果至少有 y 个 key 发生变化就会触发命令进行持久化操作。
它的优点:
- 快速加载:RDB 生成的快照文件是压缩的二进制文件,适合备份和灾难恢复。
- 低资源占用:RDB 持久化在 Redis 主线程之外进行,不会对主线程产生太大影响。
它的缺点:
- 数据丢失风险:由于 RDB 是间隔性保存的快照,如果 Redis 崩溃,可能会丢失上次保存快照后的数据。
RDB 持久化命令:
- save:在主线程生成 RDB 文件,因此生成其间,主进程无法执行正常的读写命令,需要等待 RDB 结束。
- bgsave:利用 Fork 操作得到子进程,在子进程执行 RDB 生成,不会阻塞主进程,默认使用 bgsave。
bgsave 流程(重点):
1)检查子进程(检查是否存在 AOF/RDB 的子进程正在进行),如果有返回错误
2)触发持久化,调用 rdbSaveBackground
3)开始 fork,子进程执行 rdb 操作,同时主进程响应其他操作。
4)RDB 完成后,替换原来的旧 RDB 文件,子进程退出。
注意事项(重点):
1)Fork 操作会产生短暂的阻塞,微秒级别操作过后,不会阻塞主进程,整个过程不是完全的非阻塞。
2)RDB 由于是快照备份所有数据,而不是像 AOF 一样存写命令,因为 Redis 实例重启后恢复数据的速度可以得到保证,大数据量下比AOF 会快很多。
3)Fork 操作利用了写时复制,类似与 CopyOnWriteArrayList 。
小知识-写时复制:
在子进程创建时,它不会直接将主进程地址空间全部复制,而是共享同一个内存。
之后如果任意一个进程需要对内存进行修改操作,内存会重新复制一份提供给修改的进程单独使用。
更多可看:
AOF 持久化详解
AOF 持久化机制是指将 Redis 写命令以追加的形式写入到磁盘中的 AOF 文件,AOF 文件记录了 Redis 在内存中的操作过程,只要在 Redis 重启后重新执行 AOF 文件中的写命令即可将数据恢复到内存中。
AOF 机制的优点:
- AOF 机制比 RDB 机制更加可靠,因为 AOF 文件记录了 Redis 执行的所有写命令,可以在每次写操作命令执行完毕后都落盘存储。
AOF 机制的缺点:
- AOF 机制生成的 AOF 文件比 RDB 文件更大,当数据集比较大时,AOF 文件会比 RDB 文件占用更多的磁盘空间。
- AOF 机制对于数据恢复的时间比 RDB 机制更加耗时,因为要重新执行 AOF 文件中的所有操作命令。
混合持久化
RDB 和 AOF 都有各自的缺点。
如果 RDB 备份的频率低,那么丢的数据多。备份的频率高,性能影响大。
AOF 文件虽然丢数据比较少,但是恢复起来又比较耗时。
因此 Redis 4.0 以后引入了混合持久化,通过 aof‐use‐rdb‐preamble 配置开启混合持久化。
当 AOF 重写的时候(注意混合持久化是在 aof 重写时触发的)。它会先生成当前时间的 RDB 快照,将其写入新的 AOF 文件头部位置。
这段时间主线程处理的操作命令会记录在重写缓冲区中,RDB 写入完毕后将这里的增量数据追加到这个新 AOF 文件中,最后再用新 AOF 文件替换旧 AOF 文件。
如此一来,当 Redis 通过 AOF 文件恢复数据时,会先加载 RDB,然后再重新执行指令恢复后半部分的增量数据,这样就可以大幅度提高数据恢复的速度!
AOF 写回策略
AOF 提供了三种写回策略,决定何时将数据同步到磁盘中:
- always:每次写操作后立即调用 fsync,将数据同步到磁盘。这种策略保证了最高的数据安全性,但也会显著降低性能,因为每个写操作都要等待磁盘写入完成。
- everysec:每秒调用一次 fsync,将数据同步到磁盘。这种策略在性能和数据安全性之间做了折中,默认情况下,Redis 使用这种策略。最多会丢失 1 秒的数据。
- no:由操作系统决定何时将数据写入磁盘。通常,操作系统会在一定时间后或缓冲区满时同步数据到磁盘。这种策略具有最高的性能,但数据安全性较低,因为在 Redis 崩溃时可能会丢失较多的数据。
设置 always 能一定保证数据不丢失吗?
答案是不能!因为 Redis 是先执行命令再写入 aof,所以如果执行命令写入 aof 这段时间 Redis 宕机了,重启后也无法利用 aof 恢复!
所以 Redis 的持久化机制,并不能保证数据不丢失!
AOF 重写机制
AOF 文件随着写操作的增加会不断变大,过大的 AOF 文件会导致恢复速度变慢,并消耗大量磁盘空间。所以,Redis 提供了 AOF 重写机制,即对 AOF 文件进行压缩,通过最少的命令来重新生成一个等效的 AOF 文件。
拿 key A 举个例子,AOF 记录了每次写命令如 set A 1、set A 2、set A 3。实际上前面的 set A 1、set A 2 是历史值,我们仅关心最新的值,因此 AOF 重写就是仅记录数据的最终值即可,即set A 3,这样 AOF 文件就“瘦身”了。
注意:AOF 重写并不是对现有的 AOF 文件进行修改,而是根据当前每个键的最新值转换为对应的写命令,写入新的 AOF 文件,形成一个新文件。
AOF 重写流程如下:
- 创建子进程:Redis 使用 BGREWRITEAOF 命令创建一个子进程,负责 AOF 重写操作。
- 生成新的 AOF 文件:子进程根据当前数据库的状态,将每个键的最新值转换为对应的写命令,并写入新的 AOF 文件。例如,对于一个列表键,子进程会使用一条 RPUSH 命令将所有元素写入,而不是记录之前的多次操作。
- 处理新写入的命令:在重写过程中,主进程仍然处理新的写操作。为了避免数据不一致,主进程会将这些新的写命令同时追加到现有的 AOF 文件和一个缓冲区(
aof_rewrite_buf)中。 - 合并新命令:当子进程完成新的 AOF 文件的写入后,主进程会将缓冲区中的新命令追加到新的 AOF 文件中,确保其包含所有最新的操作。
- 替换旧的 AOF 文件:最后,Redis 使用新的 AOF 文件替换旧的文件,实现 AOF 文件的重写。
AOF 重写可以通过手动触发或自动触发:
1)手动触发:使用 BGREWRITEAOF 命令可以手动触发 AOF 重写。
2)自动触发:通过配置文件中的参数控制自动触发条件,参数如下:
auto-aof-rewrite-min-size:AOF 文件达到该大小时允许重写(默认 64 MB)。auto-aof-rewrite-percentage:当前 AOF 文件大小相对于上次重写后的增长百分比达到该值时触发重写。
Redis 7.0 MP-AOF(Multi-Part Append Only File)
7.0 之前的 AOF 重写有三大问题:
- 内存开销:
aof_buf和aof_rewrite_buf中大部分内容是重复的。 - CPU 开销:主进程需要花费 CPU 时间往
aof_rewrite_buf写入数据,并向子进程发送aof_rewrite_buf中的数据。子进程需要消耗 CPU 时间将aof_rewrite_buf写入新 AOF 文件。 - 磁盘开销:
aof_buf数据会写到当前的 AOF 文件,aof_rewrite_buf数据写到新的 AOF 文件,一份数据需要写两次磁盘。
针对以上问题 Redis 7.0 引入了 MP-AOF(Multi-Part Append Only File)机制。简单来说就是将一个 AOF 文件拆分成了多个文件:
- 一个基础文件(base file),代表数据的初始快照
- 增量文件(incremental files),记录自基础文件创建以来的所有写操作,可以有多个
- 基础文件和增量文件都会存放在一个单独的目录中,并由一个清单文件(manifest file)进行统一跟踪和管理
大致流程如下:
可以看到,重写期间的数据变更直接写到 aof_buf 再到新的增量 AOF 文件中,避免了之前多个 buf 的写入。
且子进程独立重写基础的 AOF,于主进程无交互,节省了主进程的 CPU 开销。
当重写完毕后,仅需更新 manifest 文件,加入新的增量 AOF 文件和基础 AOF 文件,然后将之前的增量 AOF 文件和基础 AOF 文件标记为历史文件(会被异步删除)即可。更新完 manifest 就代表 AOF 重写结束。
AOF 文件修复
如果 AOF 文件因系统崩溃等原因损坏,可以使用 redis-check-aof 工具修复。该工具会截断文件中的不完整命令,使其恢复到一致状态。