最新:Redis内存——内存消耗(内存都去哪了?)

最新:Redis持久化——如何选择合适的持久化方式

最新:Redis持久化——AOF日志

更多文章...

上一篇文章Redis持久化——内存快照(RDB)我们总结到使用Redis内存快照进行持久化,在t 时刻做了一次快照,然后又在 t+n 时刻做了一次快照,此时如果宕机,则会丢失在此期间内修改的数据。但又不能频繁的进行内存快照,那么有什么办法能够尽可能的减少这种数据丢失呢?Redis提供了另一种持久化的方式——AOF日志(Append Only File)。

什么是AOF日志持久化

执行后写日志

与内存快照保存当前内存中的数据所不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。即每执行一个命令,就会把该命令写到日志文件里。

需要注意的是写日志的操作在Redis执行命令将数据写入内存之后,如下图所示:

Redis持久化——AOF日志-LMLPHP

这样做的好处就是不会阻塞当前操作,也可以避免额外的检查开销,如果是在命令执行前进行写日志的操作,一旦命令语法是错误的,不进行检查的话就会导致写入到日志文件中的命令是错误的,在使用日志文件恢复数据的时候就会出错。而在命令执行后在进行日志的写入则不会有这个问题。

但是也存在两个问题,

  1. AOF 虽然避免了对当前命令的阻塞,但却可能会给下一个操作带来阻塞风险。因为,AOF 日志是在主进程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了

  2. 如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。如果此时 Redis 是用作缓存,还可以从后端数据库重新读入数据进行恢复,但是,如果 Redis 是直接用作数据库的话,此时,因为命令没有记入日志,所以就无法用日志进行恢复了。

AOF 缓冲区

针对上面两个问题,Redis提供了缓冲区的方式进行AOF日志的记录,以达到尽可能的避免阻塞和数据丢失的问题。

Redis在执行完命令进行持久化的时候,并非直接写入磁盘日志文件,而是先写入AOF缓冲区内,之后再通过某种策略写到磁盘。

Redis持久化——AOF日志-LMLPHP

使用缓存区的方式进行AOF日志的记录,上面提到的两个问题其实就和日志从缓冲区写入磁盘的时机有关系。

三种回写策略

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秒内的数据丢失。

在真正使用中,我们可以根据具体对性能和数据完整性的要求,分析这三种回写策略,选择适合的策略来进行持久化。

AOF重写

日志文件越来越大怎么办

选择了合适的回写策略,AOF这种持久化的方式还有其它问题吗?

因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着时间的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,过大的AOF文件不仅追加命令会变慢,而且可能对Redis服务器、甚至整个宿主计算机造成影响,并且AOF文件的体积越大,使用AOF文件来进行数据还原所需的时间就越多。

这个时候就要用到AOF重写机制了

redis> set testKey testValue
OK
redis> set testKey testValue1
OK
redis> del testKey
OK
redis> set testKey hello
OK
redis> set testKey world
OK

AOF 文件是以追加的方式,逐一记录接收到的写命令的。当一个键值对被多条写命令反复修改时,AOF 文件会记录相应的多条命令。如上示例,我们执行完命令后,Redis会在AOF里面追加5条命令。但实际上只需要set testKey world一条命令就够了。

AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。比如说,当读取了键值对“testkey”: “world”之后,重写机制会记录 set testkey world这条命令。这样,当需要恢复时,可以重新执行该命令,实现“testkey”: “world”的写入。

这样,重写后的日志,从5条变成了1条,而对于可能被修改过成百上千次的键值对来说,重写能节省的空间就更大了。

虽然 AOF 重写后,日志文件会缩小,但是,要把整个数据库的最新数据的操作日志都写回磁盘,仍然是一个非常耗时的过程。这时,我们不得不关注:重写会不会导致阻塞?这就要看看AOF重写的过程是怎么样的

AOF 重写过程

因为AOF重写也是一个非常耗时的过程,又因为Redis单线程的特性,同内存快照一样,AOF重写的过程也是由父进程fork出bgrewriteaof子进程来完成的.

使用子进程(而不是开启一个线程)进行AOF重写虽然可以避免使用锁的情况下,保证数据安全性,但是会带来子进程和父进程一致性问题。
例如在开始重写之后父进程又接收了新的键值对此时子进程是无法知晓的,当子进程重写完成后的数据库和父进程的数据库状态是不一致的。

如下表:

在T6时刻服务器进程有了4个键,而子进程却只有1个键

为了解决这种不一致性,Redis设置了一个AOF重写缓冲区。

Redis持久化——AOF日志-LMLPHP

在子进程执行AOF重写期间。服务器进程需要执行以下3个动作:

  1. 执行客户端命令
  2. 执行后追加到AOF缓冲区
  3. 执行后追加到AOF重写缓冲区

子进程完成AOF重写后,它向父进程发送一个信号,父进程收到信号后会调用一个信号处理函数,该函数把AOF重写缓冲区的命令追加到新AOF文件中然后替换掉现有AOF文件。父进程处理完毕后可以继续接受客户端命令调用,可以看出在AOF后台重写过程中只有这个信号处理函数会阻塞服务器进程。
下表是完整的AOF后台重写过程:

这样就可以保证重写日志期间的所有操作也都会写入新的AOF文件。

需要注意的是, T7 T8执行的任务会阻塞服务器处理命令。

总的来说,就是每次 AOF 重写时,Redis 会先fork出一个子进程用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。

AOF文件恢复

Redis 服务器重启后,会优先去载入AOF日志文件。因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。

而由于Redis命令只能在客户端上下文中执行,Redis会创建一个没有网络连接的伪客户端来执行AOF文件中的内容。

小结

本文主要总结了Redis AOF 持久化的方式,介绍了它同步磁盘的三种策略,以及日志文件过大时如何进行重写。我们知道Redis持久化方式有AOF和RDB两种,那么这两种持久化方式各自有什么优点和缺点?真正使用中我们应该如何去选择合适的持久化方式,又可能遇到哪些问题呢?我们下一篇文章继续总结

系列文章:

最新:Redis内存——内存消耗(内存都去哪了?)

最新:Redis持久化——如何选择合适的持久化方式

最新:Redis持久化——AOF日志

Redis持久化——内存快照(RDB)

一文回顾Redis五大对象(数据类型)

Redis对象——有序集合(ZSet)

Redis对象——集合(Set)

Redis对象——列表(List)

Redis对象——哈希(Hash)

Redis数据结构——quicklist

Redis对象——字符串

Redis对象——Redis对象系统简介

Redis数据结构——压缩列表

Redis数据结构——整数集合

Redis数据结构——跳跃表

Redis数据结构——字典

Redis数据结构——链表

Redis数据结构——简单动态字符串SDS

-----END-----

关注下方公众号,回复“Redis”,可得Redis相关学习资料

04-26 13:43