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 demo1
,set demokey demo2
重写会只记录当前key的最终状态,也就是 set demokey demo2
。
有两个配置项在控制AOF重写的触发时机,AOF文件大小同时超出这两个配置项时,会触发AOF重写。
- auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
- 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。
- save:在主线程中执行,会阻塞主线程,
- bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,是 Redis RDB 文件生成的默认配置。
bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件。
主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本(键值对 C’)。然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据(键值对 C)写入 RDB 文件。
RDB 频率
频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
bgsave 子进程需要通过 fork 操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程了(所以,在 Redis 中如果有一个 bgsave 在运行,就不会再启动第二个 bgsave 子进程)。
- 增量快照,就是指,做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。
- AOF RDB 混用,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。这样快照不用很频繁地执行,避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
总结备注
- 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择。
- 如果允许分钟级别的数据丢失,可以只使用 RDB。
- 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。