rdb
RDB方式,是将redis某一时刻的数据持久化到磁盘中,是一种快照式的持久化方法。
有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE。
RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件。
如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。只
有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。
save
SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求:
bgsave
BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求:
在这个命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的方式会和平时有所不同。
- 客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和BGSAVE命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个rdbSave调用,防止产生竞争条件
- 客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件。
- BGREWRITEAOF和BGSAVE两个命令不能同时执行
- 如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。
- 如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。
在redis.conf的配置文件中,就有这样的条件来触发bgsave的命令
上面的意思我那第一个来举例说明
服务器在900秒之内,对数据库进行了至少1次修改。
只要满足上面任意一个条件都会触发bgsave的命令
问题来了,怎么自动触发bgsave那?
肯定是要有一个方法来触发的,就是服务器周期性操作函数serverCron,默认每隔100毫秒就会执行一次,其实的一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE命令。
结构
aof
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的
实现
AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
写入和同步
Redis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需要定时运行的函数。
因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面
flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项的值来决定
如果用户没有主动为appendfsync选项设置值,那么appendfsync选项的默认值为everysec
先写入后同步
假设服务器在处理文件事件期间,执行了三个写入命令,如果这时flushAppendOnlyFile函数被调用,假设服务器当前appendfsync选项的值为everysec,并且距离上次同步AOF文件已经超过一秒钟,那么服务器会先将aof_buf中的内容写入到AOF文件中,然后再对AOF文件进行同步
载入
如果在开启aof之后,在redis程序启动之后,会优先加载aof的内容。
文件载入过程
重写
为什么说要进行重写那?
主要是因为如果你对一个key进行了增删改查一通操作之后,我如果要保存你这个key修改过后的状态,直接就可以用更少的代码来实现,从而实现让aof文件体积变小的目的。新的aof文件中,不包含任何的冗余代码,体积还小 岂不美哉?
后台重写
上面介绍的AOF重写程序aof_rewrite函数可以很好地完成创建一个新AOF文件的任务,但是,因为这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞,因为Redis服务器使用单个线程来处理命令请求,所以如果由服务器直接调用aof_rewrite函数的话,那么在重写AOF文件期间,服务期将无法处理客户端发来的命令请求。
所以就有了后台重写。
主进程会fork一个子进程来进行重写
- 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性
详细流程
因为子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。所以产生了aof缓冲区的概念。这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
当AOFRW被触发执行时,Redis首先会fork一个子进程进行后台重写操作,该操作会将执行fork那一刻Redis的数据快照全部重写到一个名为temp-rewriteaof-bg-pid.aof的临时AOF文件中。
由于重写操作为子进程后台执行,主进程在AOF重写期间依然可以正常响应用户命令。因此,为了让子进程最终也能获取重写期间主进程产生的增量变化,主进程除了会将执行的写命令写入aof_buf,还会写一份到aof_rewrite_buf中进行缓存。在子进程重写的后期阶段,主进程会将aof_rewrite_buf中累积的数据使用pipe发送给子进程,子进程会将这些数据追加到临时AOF文件中(详细原理可参考[1])。
当主进程承接了较大的写入流量时,aof_rewrite_buf中可能会堆积非常多的数据,导致在重写期间子进程无法将aof_rewrite_buf中的数据全部消费完。此时,aof_rewrite_buf剩余的数据将在重写结束时由主进程进行处理。
当子进程完成重写操作并退出后,主进程会在backgroundRewriteDoneHandler 中处理后续的事情。首先,将重写期间aof_rewrite_buf中未消费完的数据追加到临时AOF文件中。其次,当一切准备就绪时,Redis会使用rename 操作将临时AOF文件原子的重命名为server.aof_filename,此时原来的AOF文件会被覆盖。至此,整个AOFRW流程结束。
MP-AOF
阿里团队贡献的,redis7.0的对于aof文件的优化。
顾名思义,MP-AOF就是将原来的单个AOF文件拆分成多个AOF文件。在MP-AOF中,我们将AOF分为三种类型,分别为:
BASE:表示基础AOF,它一般由子进程通过重写产生,该文件最多只有一个。
INCR:表示增量AOF,它一般会在AOFRW开始执行时被创建,该文件可能存在多个。
HISTORY:表示历史AOF,它由BASE和INCR AOF变化而来,每次AOFRW成功完成时,本次AOFRW之前对应的BASE和INCR AOF都将变为HISTORY,HISTORY类型的AOF会被Redis自动删除。
为了管理这些AOF文件,我们引入了一个manifest(清单)文件来跟踪、管理这些AOF。同时,为了便于AOF备份和拷贝,我们将所有的AOF文件和manifest文件放入一个单独的文件目录中,目录名由appenddirname配置(Redis 7.0新增配置项)决定