除了RDB持久功能之外,Redis还提供了AOF(Append Only File)持久化功能。
- RDB 持久化通过保存数据库中的键值对来记录数据库状态
- AOF持久化是通过保存Redis 服务器锁执行的写命令来记录数据库状态

被写入AOF文件的命令是以Redis的命令请求协议格式保存的,Redis的命令请求协议是纯文本格式,可直接打开AOF文件,观察内容
AOF 持久化的实现
AOF持久化功能的实现可以分为命令的追加(append)、文件写入、文件同步(sync)三个步骤
命令追加
当AOF持久化功能处于打开状态时,服务器在执行一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾,为什么要在写命令之后再去写日志
-
优点:
- 避免额外的检查开销:Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
-
缺点:
- AOF磁盘占用更大:由于存储的是命令,所以会占用更多的磁盘
- AOF重放日志耗时
为什么 mysql 是先写日志再执行命令,而Redis 是先执行命令而后写日志?
答:是由于两者的应用场景不一致。
MySQL是关系型数据库,为了保证数据的一致性和可靠性。在执行命令之前先将命令写入日志 redo log中,等到命令执行成功后再将数据写入磁盘中。这样即使在执行命令时出现异常或者系统崩溃,也可以通过redo log进行数据恢复。
Redis是一种内存型数据库,数据持久化属于低频要求操作。在执行修改操作时,Redis会将命令写入AOF缓存区中,等到缓存区积累到一定的数据量或者经过一定的时间后,才将数据写入磁盘中,避免频繁的磁盘写入,提高Redis的性能。Redis在命令持久化上不需要像MySQL那样保证绝对的数据一致性和可靠性,可以采用先执行命令后写入日志的方式
写会策略
这里的同步是操作系统在write 写入文件时,通常将数据保存在一个内存缓冲区,然后将缓冲区中的数据写入到磁盘的过程
Redis的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求以及回复,时间事件则负责执行像serverCron函数这样需要定时运行的函数
服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以每次结束一个事件循环之前,都会调用 flushAppendOnlyFile 函数,考虑是否将aof_buf缓冲区中的内容写入和保存到AOF文件里面
1 | def eventLoop(): |
通过服务器配置的 appendfsync选型的值来决定AOF持久化行为
| Appendfsync | 写回策略 | 效率 | 安全 |
|---|---|---|---|
| always | 将aof_buf缓冲区中的所有内容写入并同步到AOF文件 |
最慢 | 最高 |
| everysec | 将aof_buf缓冲区的所有内容写入到AOF文件,如果上次同步AOF文件的时间距离现在超过1秒钟,那么子进程对AOF文件进行同步,并且这个同步操作是由一个线程专门负责执行的 |
适中 | 适中 |
| no | 将aof_buf缓冲区的中的所有内容写入到AOF文件,但并不对AOF文件进行同步,何时同步由操作系统自己决定 |
最快 | 最低 |
为了提高文件的写入效率,在现代操作系统中,用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满,或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面
导致当计算机发生停机的时候,内存缓冲区的写入数据可能会丢失。为此,系统提供了 fsync 和 fdatashync 两个同步函数,强制操作系统立即将缓冲区中的数据写入到硬盘
AOF文件的载入与数据还原
由于AOF 文件里包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令
Redis 读取AOF文件并还原数据库状态的详细步骤如下:
- 创建一个不带网络链接的伪客户端(fake client):因为 Redis 的命令智能在客户端上下文中执行,而载入AOF文件时锁使用的命令直接来源于AOF 文件而不是网络连接,所以服务器使用一个伪客户端来执行AOF文件保存的写命令
- 从AOF文件中分析并读取出一条写命令
- 使用伪客户端执行被读出的谢命令
- 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止
AOF 重写
AOF持久化是通过保存被执行的写命令来记录数据库状态,随着服务器运行时间的流逝,AOF文件中的内容会越来越多。为了解决AOF文件体积膨胀的问题,Redis提供的AOF文件重写(rewrite)功能
有两个配置项控制AOF重写的触发:
auto-aof-rewrite-min-size:表示运行AOF重写时文件的最小大小,默认为64MB。auto-aof-rewrite-percentage:这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值。
重写的原理
Redis 将生成新AOF文件替换旧AOF文件的功能命令为 “AOF文件重写”。但实际上,AOF文件重写并不需要对现有的AOF文件进行任何读取、分析或写入操作,是通过读取服务器当前的数据库状态来实现的
例如:
保存当前list键(含有6个value)的状态,须在AOF文件中写六条命令,如果服务器想尽量少的命令来记录list键的状态,最简单高效的办法不是去读取和分析现有AOF文件,而是直接从数据库中读取键list的值,用 一条 RPUSH key value... 来代替保存在AOF文件中的六条命令
在实际执行过程中,为了避免执行命令时造成客户端输入缓冲区溢出,会检查键锁包含的元素个数,如果数据超过 redis.h/REDIS_AOF_REWRITE_ITEMS_PRE_CMD 的值,那么重写程序将使用多条命令来记录键的值,而不单单使用一条命令
AOF后台重写
AOF重写程序 aof_rewrite 函数可以很好地完成创建一个新AOF文件的任务,但这个函数进行了大量的写入操作,所以调用这个函数的线程将会被长时间阻塞。将其放到子进程里执行可以达到两个目的:
- 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,避免使用锁的情况下,保证数据安全性
子进程在处理AOF重写期间,服务器还需要继续处理命令请求,可能导致新的数据库状态与AOF文件所保存的数据库状态不一致
如图:新的AOF文件只保存了k1一个键的数据,而服务器数据库现在却有k1、k2、k3、k4 四个键
为了解决这种数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用
当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区
步骤如下:
- 执行客户端发来的命令
- 将执行后的写命令追加到AOF缓冲区
- 将执行后的写命令追加到AOF重写缓冲区
这样可以保证:
- AOF缓冲区的内容讲定期被写入和同步到AOF文件,对现有AOF文件的处理工作正常进行
- 从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区里面
当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接到该信号之后,会调用一个信号处理函数,并执行以下工作:
- 将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致
- 对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换
这个信号处理函数执行完毕之后,父进程就可以像往常一样接受命令请求
整个AOF后台重写过程,只有信号处理函数执行时会对服务器进程(父进程)造成阻塞,在其他时候,AOF后台重写都不会阻塞父进程
主线程fork出子进程的是如何复制内存数据的
fork采用操作系统提供的写时复制**(copy on write)**机制,避免一次性拷贝大量内存数据给子进程造成阻塞。fork子进程时,子进程时会拷贝父进程的页表,即虚实映射关系(虚拟内存和物理内存的映射索引表),而不会拷贝物理内存。这个拷贝会消耗大量cpu资源,并且拷贝完成前会阻塞主线程,阻塞时间取决于内存中的数据量,数据量越大,则内存页表越大。拷贝完成后,父子进程使用相同的内存地址空间。
主进程可以有数据写入,这时候就会拷贝物理内存中的数据。如下图(进程1看做是主进程,进程2看做是子进程):
在主进程有数据写入时,而这个数据刚好在页c中,操作系统会创建这个页面的副本(页c的副本),即拷贝当前页的物理数据,将其映射到主进程中,而子进程还是使用原来的的页c
在重写日志整个过程时,主线程有哪些地方会被阻塞?
- fork子进程时,需要拷贝虚拟页表,会对主线程阻塞。
- 主进程有bigkey写入时,操作系统会创建页面的副本,并拷贝原有的数据,会对主线程阻塞。
- 子进程重写日志完成后,主进程追加aof重写缓冲区时可能会对主线程阻塞。
为什么AOF重写不复用原AOF日志?
- 父子进程写同一个文件会产生竞争问题,影响父进程的性能。
- 如果AOF重写过程中失败了,相当于污染了原本的AOF文件,无法做恢复数据使用。
持久化配置
1 | appendonly参数开启AOF持久化 |
混合模式
Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样,快照不用很频繁地执行,避免了频繁 fork 对主线程的影响。AOF 日志也只用记录两次快照间的操作,降低了出现文件过大的风险,减少重写开销。
如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。
总结
-
AOF文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态
-
AOF文件中国暖的所有命令都以Redis命令请求协议的格式保存
-
命令请求会先保存到AOF缓冲区,之后再定期写入并同步到AOF文件
-
appendfsync选项的不同值对AOF持久化功能的安全性以及Redis服务器的性能有很大的影响
-
服务器只要载入并重新执行保存在AOF文件中的命令,就可以还原数据库状态
-
AOF重写可以产生一个新的AOF文件,新的AOF文件和原有的AOF文件所保存的数据库状态一样。但体积更小
-
AOF重写是有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无需对现有AOF文件进行任何读入、分析或写入操作
-
执行BGREWRITEAOF命令时,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,是的新旧两个AOF文件所保存的数据库状态一致。最后通过新的AOF替换旧的AOF文件,以此来完成AOF文件重写操作