redis的持久化机制
Redis的持久化机制
Redis的持久化机制
首先redis大致有四种持久化的机制:
- 全用缓存(显然不可能)
- RDB快照
- AOF日志
- RDB/AOF混用机制
RDB
RDB 持久化通过创建快照来获取内存某个时间点上的副本,利用快照可以进行方便地进行主从复制,默认快照文件为 dump.rdb。
redis.conf 文件可以配置在 x 秒内如果至少有 y 个 key 发生变化就会触发命令进行持久化操作。默认是: save 900 1 每900s,有1条写数据操作; save 300 10 每300s,有10条写数据操作; save 60 10000 每60s,有10000写数据操作。 满足任意一个就会触发rdb持久化,这里使用的是bgsave,后台执行rdb持久化。
除了主动执行save,bgsave,以及达到redis.conf里配置的持久化策略外,在用户关闭redis的时候会进行最后一次rdb持久化,以记录更全面的数据,所以正常关闭不会丢失数据,崩溃才会
它的优点:
- 快速加载: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。
小知识-写时复制:
在子进程创建时,它不会直接将主进程地址空间全部复制,而是共享同一个内存。原因在于如果全量复制,那么fork的执行时间可能过长,可能会阻塞正常的主进程的业务,并且如果主进程有10G,那么复制后将会是20G,极大概率oom。所以写时复制,就使得子进程在做rdb持久化的时候很轻很快,且不会阻塞主进程的业务。
之后如果任意一个进程需要对内存进行修改操作,内存会重新复制一份提供给修改的进程单独使用。
RDB本质是Redis的数据快照,这种方式是最常见、最稳定的数据持久化手段,Redis中RDB的触发方式有三种:达到阈值周期函数触发、正常关闭Redis服务触发、主动执行BGSAVE命令或SAVE触发。Redis是通过fork一个子进程的方式来进行RDB,配合写时复制技术,相当于是异步执行,和主进程互不干扰,将对执行流程的影响降到最低。
Redis 在生成 RDB 文件时如何处理请求?
在 Redis 生成 RDB 文件时是异步的(使用 bgsave 命令),采用了 fork 子进程的方式来进行快照操作。生成 RDB 文件的过程由子进程执行,主进程继续处理客户端请求,所以可以保证 Redis 在生成快照的过程中依然对外提供服务,不会影响正常请求。
此时就运用了写时复制的技术。
当主进程 fork 出一个子进程后,并不会把主进程的所有内存数据重新复制一份给子进程,而是让主进程和子进程共享相同的内存页面。
底层的实现仅仅复制了页表,但映射的物理内存还是同一个。这样做可以加快 fork 的速度,减少性能损耗(fork会阻塞主进程)。

此时,主进程收到写命令,需要修改数据,那么主进程会将对应数据所在的页复制一份,并让主进程的页表指向这个复制页,然后对副本进行修改。此时子进程指向的还是老的页,因此数据没有变化,符合快照的概念。

通过在写的时候才触发内存的复制,可以显著地降低 Redis 实例的性能压力,最大限度的减少 RDB 对服务正常运行的影响。
避免高峰期生成 RDB
如果 RDB 时间长,且写并发高,此时会被系统产生比较大的影响。
原因是因为写时复制时,如果共享的每一页内存都被修改,就会使得内存极速膨胀,最大内存可以膨胀两倍,所以要注意内存的使用量,防止内存过载。
RDB 会产生大量的磁盘 I/O,要注意磁盘性能导致的影响。
还需要注意 CPU 负载,毕竟有大量的数据需要写入。
因此如果 RDB 在高峰期可能会影响到正常业务,需要合理安排生成 RDB 的时机。
AOF
AOF 持久化机制是指将 Redis 写命令以追加的形式写入到磁盘中的 AOF 文件,AOF 文件记录了 Redis 在内存中的操作过程,只要在 Redis 重启后重新执行 AOF 文件中的写命令即可将数据恢复到内存中。
AOF 机制的优点:
- AOF 机制比 RDB 机制更加可靠,因为 AOF 文件记录了 Redis 执行的所有写命令,可以在每次写操作命令执行完毕后都落盘存储。
AOF 机制的缺点:
- AOF 机制生成的 AOF 文件比 RDB 文件更大,当数据集比较大时,AOF 文件会比 RDB 文件占用更多的磁盘空间。
- AOF 机制对于数据恢复的时间比 RDB 机制更加耗时,因为要重新执行 AOF 文件中的所有操作命令。
AOF 写回策略
AOF 提供了三种写回策略,决定何时将数据同步到磁盘中:
- always:每次写操作后立即调用 fsync,将数据同步到磁盘。这种策略保证了最高的数据安全性,但也会显著降低性能,因为每个写操作都要等待磁盘写入完成。
- everysec:每秒调用一次 fsync,将数据同步到磁盘。这种策略在性能和数据安全性之间做了折中,默认情况下,Redis 使用这种策略。最多会丢失 1 秒的数据。
- no:由操作系统决定何时将数据写入磁盘。通常,操作系统会在一定时间后或缓冲区满时同步数据到磁盘。这种策略具有最高的性能,但数据安全性较低,因为在 Redis 崩溃时可能会丢失较多的数据。
redis的写入流程:
- 执行写命令(修改内存)
- 追加aof格式命令到aof_buf缓冲区(用户态)
- 调用write() 将aof_buf中的内容写入page_cache(操作系统内核缓冲区)
- 调用fsync() 刷盘
注意的是:上述的写回策略指的就是第四步,如果是always则立刻刷盘,everysec一秒刷一次,no则是完全交给操作系统来刷盘。因为
那write()又是何时执行的呢? 事实上,Redis源码中一共有4个时机,会调用一个叫flushAppendOnlyFile的函数,这个函数会使用write函数来将数据写入操作系统缓冲区:
- beforesleep
- servercorn
- 服务器退出redis前
- 关闭aof功能时
第三点和第四点很好理解,在退出redis之前,如果不把aof_buf全部刷新到page_cache中那就会导致数据缺失,正常的退出不应该丢失数据,所以这一点和rdb一样,在退出时都会做最后的数据持久化。第四点则是关闭aof的时候,说明你不需要aof这个功能了,但是之前aof所记录的数据必须要完整,所以在关闭aof之后会执行一次aof_buf的刷新
serverCorn则是一个定时任务,他用做aof和rdb的兜底flush,清理过期的key,内存统计,Cluster心跳等等。为什么需要他呢?因为redis是事件驱动的,如果没有请求进来,没有时间触发,很多事情可能永远都不会做。
而beforesleep则不是定时任务,而是事件循环的一部分hook,更准确地说:每一轮事件循环,在 Redis 阻塞等待新事件之前,都会调用一次。他的语义是:这一轮我已经把我能处理的事情处理完了,然后就在正式阻塞前,调用beforesleep()。
对于上述的刷盘,如果配置的是appendfsync always,那么就会在beforesleep阶段由主线程直接调用flush函数刷盘。如果是appendfsync everysec 那么只会在beforesleep阶段标记需要fsync,然后会由aof的后台fsync线程来处理。
设置 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 重写,也是需要 fork 子线程,因此也有写时复制的机制。
AOF 重写流程如下:
- 创建子进程:Redis 使用 BGREWRITEAOF 命令创建一个子进程,负责 AOF 重写操作。
- 生成新的 AOF 文件:子进程根据当前数据库的状态,将每个键的最新值转换为对应的写命令,并写入新的 AOF 文件。例如,对于一个列表键,子进程会使用一条 RPUSH 命令将所有元素写入,而不是记录之前的多次操作。
- 处理新写入的命令:在重写过程中,主进程仍然处理新的写操作。为了避免数据不一致,主进程会将这些新的写命令同时追加到现有的aof_buf和一个缓冲区(
aof_rewrite_buf)中。 - 合并新命令:当子进程完成新的 AOF 文件的写入后,主进程会将缓冲区中的新命令追加到新的 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文件太大或达到重写条件时,Redis 基于当前主进程fork出一个子进程
- 重写期间主进程继续处理新命令,新的命令会记录在一个新的incremental file中
- 子进程基于fork的快照,生成新的base file
- 子进程生成了新的base file后,主进程会把旧的base和incremnetal file标记为历史文件
- 然后更新manifest文件,新的base文件和incremental file成为新的aof持久化状态
AOF 文件修复
如果 AOF 文件因系统崩溃等原因损坏,可以使用 redis-check-aof 工具修复。该工具会截断文件中的不完整命令,使其恢复到一致状态。
RDB/AOF混合持久化
RDB 和 AOF 都有各自的缺点。
如果 RDB 备份的频率低,那么丢的数据多。备份的频率高,性能影响大。
AOF 文件虽然丢数据比较少,但是恢复起来又比较耗时。
因此 Redis 4.0 以后引入了混合持久化,通过 aof‐use‐rdb‐preamble 配置开启混合持久化。
需要注意的是,混合持久化其实是AOF的一种重写机制,是针对AOF重写的一个优化措施
当 AOF 重写的时候(注意混合持久化是在 aof 重写时触发的)。它会先生成当前时间的 RDB 快照,将其写入新的 AOF 文件头部位置。
这段时间主线程处理的操作命令会记录在重写缓冲区中,RDB 写入完毕后将这里的增量数据追加到这个新 AOF 文件中,最后再用新 AOF 文件替换旧 AOF 文件。
如此一来,当 Redis 通过 AOF 文件恢复数据时,会先加载 RDB,然后再重新执行指令恢复后半部分的增量数据,这样就可以大幅度提高数据恢复的速度!
