Redis 的持久化功能是区别于 Memcached 显著特性,数据持久化可以保证系统在发生宕机和重启后数据不会丢失,对于 redis 这种存储在内存中的数据库显得尤为重要。 在 Redis 4.0 以前数据持久化的方式主要有两种
RDB 是将内存中的数据在某一时刻的状态记录下来以二进制的方式存储到磁盘中,通过生成一个经过压缩的二进制文件来实现数据库状态的复原。默认是 dump.rdb 文件,它的优点是以二进制存储,占用空间更小,数据存储更紧凑。
因为 RDB 文件是保存在磁盘中,哪怕 Redis 服务器宕机,Redis 服务器就可以通过该文件来还原数据库状态。
触发RDB持久化既可以通过手动执行,也可以根据服务器配置选项定期执行。主要分为手动触发和自动触发两种方式。
手动触发主要是通过save 和 bgsave 两个命令来生成RDB 文件
自动触发主要是通过 redis.conf 配置文件来完成:
# save <seconds> <changes> 周期性执行RDB持久化条件的设置格式
# 默认配置
save 900 1
save 300 10
save 60 10000
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
# 导入时是否检查,这里是使用CRC64算法来进行数据校验,会增加性能消耗
rdbchecksum yes
还有其他情况也可能会导致自动触发:
数据量较大时进行快照,用时相对会比较长。如果服务器在这个期间收到写请求,那么就不能保证快照的完整性。那么Redis 是如何做的?
Redis 使用的是操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。
如下图,针对 bgsave 命令触发 RDB 机制来说,如果主线程要修改一块数据(图中的键值对 C),这块数据就会被复制一份,生成该数据的副本(键值对C')。此时中线程在该副本上进行修改,bgsave 子线程继续把原来的数据(键值对 C)写入 RDB 文件中。
这样既保证了RDB 文件的完整性,也允许主线程同时对数据进行修改,避免了对正常业务的影响。
数据没有全部写入磁盘前,该次快照操作都是失败的。将以这次快照操作前的完整 RDB 快照文件作为恢复内存的数据。也就是此次快照操作过程不能影响上一次的备份数据。实际上在操作时 Redis 服务会在磁盘上创建一个临时文件来进行数据操作,待操作成功后才会用这个临时文件替换上一次的备份数据。
频繁的执行全量快照,能够带来更快的恢复速度。但同时也会带来大量的开销,给磁盘带来压力,多个快照竞争有限的磁盘带宽,容易造成恶性循环。此外, bgsave 子进程需要通过 fork 操作从主线程创建出来。如果频繁的fork 出 bgsave 子进程就会阻塞主线程。因此 Redis 在有一个 bgsave 运行时,就不会再启动第二个 bgsave 子进程。
这里就可以采用增量快照的方法,就是在全量快照后,后续的快照只对修改后的数据进行快照,避免对数据重复的那部分进行快照带来的消耗,这样更加高效。这里就需要对修改的文件数据进行记录,如下图所示:
但是,我们在增量快照时,记录所修改的数据信息也是一部分的开销,在大量数据修改时的记录数据,其内存开销也不少。所以对于做快照的频率,不能太高也不能太低。那么有什么解决方法既能够让 RDB 快速恢复,又能以较小的开销实现少丢数据呢?
Redis 4.0 提出了 混合使用 AOF 日志和内存快照的方法可以很好的解决上面的场景。
AOF (Append Only File)日志和大多数的数据库写入日志的方式不同,采用的是写后日志。(比如 MySQL 是通过写前日志(Write Ahead Log, WAL)和两阶段提交来实现数据和逻辑的一致性)
写后日志的意思是 Redis 先将数据写入内存,然后再记录日志,如下图:
采用后写日志有这样的好处:
但是在写回磁盘时也会产生风险:
这两个风险是 AOF 写回磁盘时机不当造成的,对于这两个风险,AOF 有三种写回策略可以避免。也就是在文件写入同步时发生。
AOF 持久化功能的实现可以分为命令追加(append)、文件写入(write)和文件同步(sync)三个步骤。
当 AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,就会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾中,这个 aof_buf 是以 sds 对象形式存储。
struct redisServer {
...
// AOF 缓冲区
sds aof_buf;
...
};
以Redis 服务端收到 set testkey testvalue
命令后的记录为例,查看 AOF 日志的运行原理。
在服务器执行 set 命令后,会将以下协议内容追加到 aof_buf 缓冲区的末尾:
*3\r\n3\r\nSET\r\n7\r\ntestkey\r\n
*3
:表示当前命令有三个部分,每个部分都是由$+数字
开头,后面紧跟具体的命令、键或值\r\n
:是换行符,具体如上图所示Redis 的服务器进程就是一个事件循环(loop),在处理文件事件时可能会执行写命令,所以在服务器每次结束一个事件循环之前,它会调用 flushAppendOnlyFile
函数,考虑是否需要将 aof_buf 缓冲区中的内容写入到AOF 文件中去。
def eventLoop():
while True:
//处理文件事件,接收命令请求以及发送命令回复
//处理命令请求时可能会有新内容被追加到 aof_buf缓冲区中
processFileEvents();
//处理时间事件
processTimeEvents();
//考虑是否要将 aof_buf中的内容写入和保存到 AOF文件里面
flushAppendOnlyFile();
而flushAppendOnlyFile
函数的行为有服务器配置的 appendfsync 选项的值来决定。其值主要有三种:
在保存了 AOF 文件后,服务器只需要重新执行 AOF 文件中的写命令后,就可以还原服务器关闭之前的数据库状态,其具体步骤如下:
因为 AOF 是通过保存被执行的写命令来完成 Redis 持久化的,所以随着 Redis 的运行时间越长,AOF 文件中存储的日志内容会越来越多。会给 Redis 服务器,带来很大的影响。因此 Redis 提供了 AOF 文件重写功能解决这一痛点。
重写机制具有“多变一”的功能,也就是对旧日志文件中的多条命令,在重写后的新日志变成了一条命令。如下图所示:
AOF 重写过程是由后台子进程 bgrewriteaof 来完成,重写过程是一个拷贝,两处日志:
所以说在每次 AOF 重写时,Redis 会先执行一份内存拷贝,用于重写;然后使用两个日志保证在重写过程中新写入的数据不会丢失。同时因为 bgrewriteaof 子进程的出现,这个过程并不会阻塞主线程。
有两个配置项可以控制 AOF 重写的触发:
auto-aof-rewrite-min-size
: 表示运行 AOF 重写时文件的最小大小,默认是 64MBauto-aof-rewrite-percentage
: 这个值是当前 aof 文件和上一次重写后 aof 文件差值,再除以上一次重写 aof 文件大小。在介绍 RDB 机制时,我们了解到是否有一种方法既能利用 RDB 的快速恢复,又能以较小的开销做到尽量少丢数据?也就是混合使用 RDB 和 AOF 两种机制的方法:就是让 RDB 快照以一定频率执行,在两次快照之间使用 AOF 日志记录这期间的所有命令操作。
如上图所示,第一次全量快照后,在T1 和 T2 时刻修改用 AOF 日志记录,等到第二次全量快照时,可以清空 AOF 日志。因为此时的修改都已经记录到快照中,恢复时也就不再使用日志了。
所以 RDB 和 AOF 混合机制综合了上述两种机制的优缺点。
AOF 的载入和还原过程在 AOF 小节中有提到。现在谈谈同时存在两者的情况,服务器如何用哪个文件来还原数据库状态:
《redis 设计与实现》
https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc