Redis持久化-AOF
什么是AOF日志持久化
1.1 执行后写日志
与内存快照保存当前内存中的数据所不同,AOF
持久化是通过保存Redis
服务器所执行的写命令来记录数据库状态的。即每执行一个命令,就会把该命令写到日志文件里。需要注意的是写日志的操作在Redis
执行命令将数据写入内存之后,如下图所示:这样做的好处就是不会阻塞当前操作,也可以避免额外的检查开销,如果是在命令执行前进行写日志的操作,一旦命令语法是错误的,不进行检查的话就会导致写入到日志文件中的命令是错误的,在使用日志文件恢复数据的时候就会出错。而在命令执行后在进行日志的写入则不会有这个问题。但是也存在两个问题,
AOF
虽然避免了对当前命令的阻塞,但却可能会给下一个操作带来阻塞风险。因为,AOF
日志是在主进程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了- 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。如果此时
Redis
是用作缓存,还可以从后端数据库重新读入数据进行恢复,但是,如果Redis
是直接用作数据库的话,此时,因为命令没有记入日志,所以就无法用日志进行恢复了。
1.2 AOF 缓冲区
针对上面两个问题,Redis
提供了缓冲区的方式进行AOF
日志的记录,以达到尽可能的避免阻塞和数据丢失的问题。即Redis
在执行完命令进行持久化的时候,并非直接写入磁盘日志文件,而是先写入AOF
缓冲区内,之后再通过某种策略写到磁盘。使用缓存区的方式进行AOF
日志的记录,上面提到的两个问题其实就和日志从缓冲区写入磁盘的时机有关系。
1.3 三种回写策略
Redis AOF
机制提供了三种回写磁盘的策略。
Always(同步写回)
: 命令写入AOF
缓冲区后调用系统fsync
操作同步到AOF
文件,fsync
完成后线程返回Everysec(每秒写回)
: 命令写人AOF
缓冲区后调用系统write
操作,write
完成后线程返回。fsync
同步文件操作由专门线程每秒调用一次No(操作系统自动写回)
: 命令写入AOF
缓冲区后调用系统write
操作,不对AOF
文件做fsync
同步,同步硬盘操作由操作系统负责,通常同步周期最长30秒
但其实可以看出这三种回写策略都并不能完美的解决问题,配置为 always
时,每次写入都要同步AOF
文件,硬盘的写入速度无法与内存相提并论,显然与 Redis
髙性能特性背道而驰配置为no
,由于操作系统每次同步AOF
文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。配置为 everysec
,是建议的同步策略,也是默认配置,虽然能做到兼顾性能和数据安全性。但极端情况下会造成1秒内的数据丢失。在真正使用中,我们可以根据具体对性能和数据完整性的要求,分析这三种回写策略,选择适合的策略来进行持久化。
回写策略 | 优点 | 缺点 |
Always(同步写回) | 可靠性高、数据基本不丢失 | 性能较差 |
Everysec(每秒写回) | 性能适中 | 宕机时丢失1秒内的数据 |
No(操作系统自动写回) | 性能好 | 宕机时丢失数据较多 |
2 AOF重写
2.1 日志文件越来越大怎么办
选择了合适的回写策略,AOF
这种持久化的方式还有其它问题吗?因为AOF
持久化是通过保存被执行的写命令来记录数据库状态的,所以随着时间的流逝,AOF
文件中的内容会越来越多,文件的体积也会越来越大,过大的AOF
文件不仅追加命令会变慢,而且可能对Redis
服务器、甚至整个宿主计算机造成影响,并且AOF
文件的体积越大,使用AOF
文件来进行数据还原所需的时间就越多。这个时候就要用到AOF
重写机制了
OK
OK
OK
OK
OK
AOF
文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF
文件会记录相应的多条命令。如上示例,我们执行完命令后,Redis
会在AOF里面追加5条命令。但实际上只需要set testKey world
一条命令就够了。AOF
重写机制就是在重写时,Redis
根据数据库的现状创建一个新的 AOF
文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。比如说,当读取了键值对“testkey”: “world”
之后,重写机制会记录 set testkey world
这条命令。这样,当需要恢复时,可以重新执行该命令,实现“testkey”: “world”
的写入。这样,重写后的日志,从5条变成了1条,而对于可能被修改过成百上千次的键值对来说,重写能节省的空间就更大了。虽然 AOF
重写后,日志文件会缩小,但是,要把整个数据库的最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程。这时,我们不得不关注:重写会不会导致阻塞?这就要看看AOF
重写的过程是怎么样的
2.2 AOF
重写过程
因为AOF
重写也是一个非常耗时的过程,又因为Redis
单线程的特性,同内存快照一样,AOF
重写的过程也是由父进程fork出bgrewriteaof
子进程来完成的.使用子进程(而不是开启一个线程)进行AOF重写虽然可以避免使用锁的情况下,保证数据安全性,但是会带来子进程和父进程一致性问题。
例如在开始重写之后父进程又接收了新的键值对此时子进程是无法知晓的,当子进程重写完成后的数据库和父进程的数据库状态是不一致的。如下表:
时间 | 服务器进程(父进程) | 子进程 |
T1 | 执行命令 SET K1 V1 | |
T2 | 执行命令 SET K1 V1 | |
T3 | 创建子进程,执行AOF文件重写 | 开始AOF重写 |
T4 | 执行命令 SET K2 V2 | 执行重写 |
T5 | 执行命令 SET K3 V3 | 执行重写 |
T6 | 执行命令 SET K4 V4 | 完成AOF重写 |
在T6时刻服务器进程有了4个键,而子进程却只有1个键为了解决这种不一致性,Redis
设置了一个AOF
重写缓冲区。在子进程执行AOF
重写期间。服务器进程需要执行以下3个动作:
- 执行客户端命令
- 执行后追加到
AOF
缓冲区 - 执行后追加到
AOF
重写缓冲区 -
子进程完成AOF
重写后,它向父进程发送一个信号,父进程收到信号后会调用一个信号处理函数,该函数把AOF
重写缓冲区的命令追加到新AOF
文件中然后替换掉现有AOF
文件。父进程处理完毕后可以继续接受客户端命令调用,可以看出在AOF
后台重写过程中只有这个信号处理函数会阻塞服务器进程。
下表是完整的AOF
后台重写过程:
时间 | 子进程服务器进程(父进程) | 子进程 |
T1 | 执行命令 SET K1 V1 | |
T2 | 执行命令 SET K1 V1 | |
T3 | 创建子进程,执行AOF文件重写 | 开始AOF重写 |
T4 | 执行命令 SET K2 V2 | 执行重写 |
T5 | 执行命令 SET K3 V3 | 执行重写 |
T6 | 执行命令 SET K4 V4 | 完成AOF重写,向父进程发送信号 |
T7 | 接收到信号,将T5 T6 T7 服务器的写命令追加到新的AOF文件末尾 | |
T8 | 用新的AOF替换旧的AOF |
这样就可以保证重写日志期间的所有操作也都会写入新的AOF文件。需要注意的是, T7 T8执行的任务会阻塞服务器处理命令。总的来说,就是每次 AOF
重写时,Redis
会先fork出一个子进程用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。
3.AOF文件恢复
在Redis
服务器重启后,会优先去载入AOF
日志文件。因为AOF
文件里面包含了重建数据库状态所需的所有写命令,所以服务器重新执行一遍AOF
文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。而由于Redis
命令只能在客户端上下文中执行,Redis
会创建一个没有网络连接的伪客户端来执行AOF
文件中的内容。
4.小结
本文主要总结了Redis AOF
持久化的方式,介绍了它同步磁盘的三种策略,以及日志文件过大时如何进行重写。我们知道Redis
持久化方式有AOF和RDB两种,那么这两种持久化方式各自有什么优点和缺点?真正使用中我们应该如何去选择合适的持久化方式,又可能遇到哪些问题呢?我们下一篇文章继续总结