AOF(Append Only File)追加写, AOF 日志它是写后日志,“写后”的意思是 Redis 是先执行命令,把数据写入内存,然后才记录日志,如下图所示:
后写日志有什么好处呢?
Redis 在写日志的时候并不会像 MySQL 那样有语法检查的,Redis 没有。所以先写日志在执行命令的话,日志中有可能记录了错误的命令,这样在故障恢复的时候可能恢复出不正确的数据。
而写后日志这样形式,让 Redis 先执行命令,如果命令不对客户端直接返回错误信息就 ok 了,正确的命令在执行完成之后在写入日志,那么 AOF 日志中的命令就都是正确的了。
AOF 后写还有一个好处就是不会阻塞当前正在执行的命令。
AOF 也有两个潜在的风险:
首先是如果刚执行完一个命令还没来得及写日志就宕机了,那么这条数据就无法恢复了。
其次,AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。
这两个风险都是和写盘相关的,其实只要是我们掌握了写盘的时机,问题就迎刃而解了。
三种写回策略
对于这个问题,AOF 机制给我们提供了三个选择,也就是 AOF 配置项 appendfsync 的三个可选值。
-
Always:同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
-
Everysec:每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
-
No:操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
针对避免主线程阻塞和减少数据丢失问题,这三种写回策略都无法做到两全其美。我们来分析下其中的原因。
- Always 可以做到不丢失数据,但是次次都落盘,代价太高了。
- Everysec 每秒写回,这个遇到特殊情况可能会丢失1s内的数据。
- No 是将落盘的时机交给操作系统,至于特殊情况下宕机,丢的数据也就不知道是多少了,因为落盘是操作系统决定的了,外接无法干预。
总体上来说,Everysec 每秒写回这个是上述三种落盘方式中折中的一种了,既考虑了主线程的性能,又考虑了落盘的时间。
这三种策略的写回时机,以及优缺点汇总在了一张表格里
到这里,我们就可以根据系统对高性能和高可靠性的要求,来选择使用哪种写回策略了。总结一下就是:想要获得高性能,就选择 No 策略;如果想要得到高可靠性保证,就选择 Always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 Everysec 策略。
每条命令执行完之后都写日志,那么日志文件太大了怎么办?
AOF 还有一个机制——重写机制,就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。
**举个例子:**就是比如 key:value 这个键值对是经过了 k:va -> ke:valu -> key:value 这样的变化过程,那么在 AOF 日志文件中也有这三条命令所对应的日志。重写就是将多变一,将三条日志记录变为一条 key:value。
不过,虽然重写后文件确实是变小了,但是要把最新的日志写回磁盘,会不会阻塞主线程呢?
和 AOF 日志由主线程写回不同,重写过程是由后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程,导致数据库性能下降。
重写的过程总结为**“一个拷贝,两处日志”。**
**“一个拷贝”**就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
因为主线程未阻塞,仍然可以处理新来的操作。此时,如果有写操作,第一处日志就是指正在使用的 AOF 日志,Redis 会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个 AOF 日志的操作仍然是齐全的,可以用于恢复。
而第二处日志,就是指新的 AOF 重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。
总结来说,每次进行 AOF 日志重写的时候,会先对数据进行拷贝;然后,使用两个日志保证在重写这个过程中,新的写入不会丢失。而且,Redis 采用了额外的线程进行数据重写,所以这个过程不会阻塞主线程。