Redis 如何持久化

前言

Redis 在作为一个缓存中间的时候和 memcache 的区别在于丰富的数据类型和可持久化缓存数据,在故障宕机后可以恢复缓存数据。Redis 的数据持久化有两种方案 AOF(Append Only File)日志和 RDB 快照。


AOF 日志

Redis 会用日志记录的方式记录下所有执行的命令,和数据库的写前日志(Write Ahead Log, WAL),实际写数据前,先把修改的数据记到日志文件中不一样,为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查,所以 Redis 是在命令执行成功后在记录日志。且不会阻塞主线程。

以收到 set demokey demo 为例,“*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。

*3
// $+{字节数}
$3
set
$7
demokey
$4
demo

写回策略

AOF 日志提供了三种日志写回策略:no、everysec、always。这三种写回策略依赖文件系统的两个系统调用完成,也就是 write 和 fsync。write 只要把日志记录写到内核缓冲区,就可以返回了,并不需要等待日志实际写回到磁盘;而 fsync 需要把日志记录写回到磁盘后才能返回,时间较长。

写回策略 写回频率 优点 缺点
Always 同步写 数据可靠性高 同步写只能在主线程,会有性能问题
No 由操作系统控制写 性能好不会阻塞主线程 如果宕机,数据丢失会比较多
Everysec 一秒写一次 居中方式 数据丢失风险

在使用 Everysec 时,Redis 允许丢失一秒的操作记录,所以,Redis 主线程并不需要确保每个操作记录日志都写回磁盘。而且,fsync 的执行时间很长,如果是在 Redis 主线程中执行 fsync,就容易阻塞主线程。所以,当写回策略配置为 Everysec 时,Redis 会使用后台的子线程异步完成 fsync 的操作。


AOF重写

文件系统本身对文件大小有限制,无法保存过大的文件;二是,如果文件太大,之后再往里面追加命令记录的话,效率也会变低;三是,如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用。

AOF重写机制指的是,对过大的AOF文件进行重写,以此来压缩AOF文件的大小。先检查当前键值数据库中的键值对,记录键值对的最终状态,从而实现对 某个键值对 重复操作后产生的多条操作记录压缩成一条的效果。进而实现压缩AOF文件的大小。
譬如执行了三条命令 set demokey demo set demokey demo1set demokey demo2 重写会只记录当前key的最终状态,也就是 set demokey demo2

有两个配置项在控制AOF重写的触发时机,AOF文件大小同时超出这两个配置项时,会触发AOF重写。

  1. auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
  2. auto-aof-rewrite-percentage: 当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。

重写是由子线程执行的。在触发重写后主线程会fork子进程,子进程是会拷贝父进程的页表,即虚实映射关系,子进程复制了父进程页表,也能共享访问父进程的内存数据。然后子进程逐一把数据记录成日志。如果在重写期间主线程还有写操作,主线程会把这个操作记录在 AOF缓冲区 和 AOF重写的缓冲区,保证重写可以记录最新的数据。
图片名称

AOF重写也有一定几率阻塞主线程。在fork子进程这个瞬间一定是会阻塞主线程的。


内存快照RDB

AOF 是以命名的方式记录。如果要恢复数据就需要执行整个 AOF 文件中的命名。如果日志过多就会恢复的很缓慢影响到正常使用。和 AOF 相比,RDB 记录的是某一时刻的数据,并不是操作,所以,在做数据恢复时,可以直接把 RDB 文件读入内存。

Redis 的快照都是全量的快照。会把内存中的所有数据都记录到磁盘中。Redis 提供了两个命令来生成 RDB 文件,分别是 save 和 bgsave。

  1. save:在主线程中执行,会阻塞主线程,
  2. bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,是 Redis RDB 文件生成的默认配置。

bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。

主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件。

图片名称

RDB 频率

频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。

bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了(所以,在 Redis 中如果有一个 bgsave 在运行,就不会再启动第二个 bgsave 子进程)。

  1. 增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
图片名称
  1. AOF RDB 混用,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。这样快照不用很频繁地执行,避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
图片名称

总结备注

  1. 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择。
  2. 如果允许分钟级别的数据丢失,可以只使用 RDB。
  3. 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
posted @ 2022-03-22 17:47  她微笑的脸  阅读(631)  评论(0编辑  收藏  举报