众所周知,Redis是一个基于内存的K-V数据库。这意味着它的所有数据都是存放在内存中,而内存由于有着断电易失性,一旦断电或者宕机,所有数据就将不复存在。因此,Redis提供了持久化方式,来帮助我们保证数据的存储可靠性。
Redis 提供了两种数据持久化机制,分别是 RDB 和 AOF. RDB 即 Redis Database 的缩写,是内存快照的意思。所谓内存快照,就是在某一时刻,将内存中的数据以文件的方式写入到磁盘上,这个文件被称为快照文件。这样一来即使遭遇宕机,也可以通过后续读取磁盘上的快照文件来快速恢复数据,避免了数据丢失。 而 AOF 则是 Append Only File 的缩写,是一种写后日志。意思是,Redis先执行命令,执行成功后,将该命令写入该日志中。与很多数据库所采取的写前日志策略不同的是,AOF 是命令执行成功后再写日志。为什么会这样呢,事实上是因为,在执行命令被写入到AOF 日志文件的过程中,为了减少性能开销,并不会对命令的语法和格式什么的进行检查。因此为了避免错误的命令被写入到日志中,只有能够正确执行并且执行成功的命令,才最终会被写入到AOF 日志中。
上面是对 Redis 两种持久化机制的简单介绍,下面展开来详细说。
1. RDB 机制
一般而言,RDB 在做快照时执行的是全量快照,有两个命令可以用于生成 RDB 快照文件,分别是 save
和 bgsave
. 二者的区别是:
save
: 在主线程中执行,会阻塞其他操作。bgsave
: 会从父进程 fork 出一个子进程,由该子进程负责创建 RDB 快照文件,主进程继续处理命令请求,不会阻塞。此处需要特别注意的一个问题是:在
bgsave
子进程对内存做快照时,内存中的数据能不能被修改呢?也就是说,主线程在快照期间能不能继续处理写请求呢?
答案显然应该是肯定的。试想一下,如果内存容量大小为2G,快照生成带宽为0.2G/s. 那么完全生成快照文件需耗时10s, 在此期间如果不能继续处理写请求,那么意味着 Redis
将有10s 的空窗期不能对外提供写入服务,这显然是难以令人接受的!
那 Redis 又是怎么做的呢? Redis 采用 操作系统的 写时复制 (Copy on Write ,简称 COW)机制来实现快照的持久化。在持久化的过程中,如果内存中某一块数据将要发生修改,那么主线程就会复制一份该数据,即生成该数据的副本,并在副本上面进行数据的修改。对应到操作系统中就是一个Page 上的数据发生了改变,父进程会复制一份该页面,并对这个复制出来的页面进行修改,而子进程对应的页面是没有改动的。子进程可以将原始未改动的对应数据依旧写入 RDB 快照文件中。
那么,Redis 快照保存什么时候会被触发呢?
一种方式是,通过手动向服务端发送命令 bgsave
. 当然也还可以基于配置文件指定触发时机自动触发 RDB 机制。
在redis.conf
配置文件中,关于 save
默认情况下有以下三种指定配置项:
- save 900 1
- save 300 10
- save 60 10000
分别表示服务器在 900s 内对数据进行了至少一次修改;在300s 内对数据进行了至少10次修改; 在 60s 内对数据进行了至少10000次修改。只要满足以上三个条件其中之一,那么 Redis 服务器就会触发 RDB 机制,自动执行 bgsave
命令。
不过呢,通常是不建议通过这种方式来触发 RDB 机制的。因为频繁的执行 bgsave
操作会带来额外的性能开销!一方面,每次做快照都是全量写入,这会给磁盘带来很大压力,同时也会增加系统的 IO 带宽负担。虽然bgsave
是 fork 出子进程来执行操作的,但是一旦这样的子进程多了,也会互相竞争有限的磁盘带宽!而且往往由于执行的频率过高,上一次快照还没做完,下一次快照就又开始了,这样竞争会被不断放大,长期下去甚至有可能拖垮你的操作系统。
2. AOF 机制
上面已经说过,AOF 是一种写后日志。只有当命令执行成功后,才会在 AOF 文件中记录下该命令。 AOF 文件中记录的是 Redis 收到的每一条命令,以文本的形式保存。如果开启了 AOF 机制,命令在成功执行后会被写到磁盘上。那么这里就有个问题:
命令一旦执行完成后,多久才会被写回磁盘呢?
针对这个问题, AOF 机制为我们提供了三个选项。 同样地,可以在 redis.conf
配置文件中对配置项appendfsync
进行指定, 该配置项有如下三个值可供选择。
- always : 表示同步写回。每个写命令执行完,立马同步地将日志写回磁盘。
- everysec: 表示每秒写回。每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘。
- no: 由操作系统来控制写回时机。每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
如果不对 appendfsync
配置项进行显式配置的话,Redis 默认的写回磁盘策略是 everysec
.
always
选项每个命令都立即写回,保证了高可靠性,但是每写一条命令都至少要进行一次磁盘IO,性能上肯定有所损耗。
no
选项将写回的时机交给了操作系统,虽然写入命令时由于没有过多的磁盘IO而获得了相对最高的性能表现,但是万一服务器发生宕机,上一次写回之后,到这一次宕机之前中间这段时间内存缓存区中积累的数据变更命令由于还没来得及写回磁盘,可就全都丢失了,因此该选项在可靠性上差了许多。
everysec
选项则兼顾了高性能和高可靠性两方面的需求,是一种折中的方案。因此,这是最被推荐使用的值。
那么,是不是配置好了写回磁盘策略,就意味着对于 AOF 机制来说,没有后顾之忧了呢?
此处答案显然是否定的!
随着写入命令的不断增多,显然 AOF 文件会变得越来越大!这会带来什么问题呢?
第一个,由于操作系统上的文件不可能无限增大,一旦操作文件系统的文件大小限制,可能这个文件就再也无法被保存起来了。第二,文件一旦过大的话,再往文件里头追加内容的话就会变得很慢,性能会急剧下降!第三,由于 AOF 文件主要用于宕机后的恢复,如果 AOF 文件过于的大,则其恢复起来也要花费相当长的时间!而这,势必会影响到服务启动后 Redis 的正常使用。为了解决这个问题,这个时候就轮到 AOF 重写机制闪亮登场了!
什么是 AOF 重写?
AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件。Redis 服务器在重写时并不会对旧的 AOF 文件进行任何分析或者读取操作,而是通过读取当前数据库中的键值对的最新状态,并为它们生成相应的写入命令,如此一来,一个键值对在新的 AOF 文件中只需要一条命令就足够了!之前对同一个键值对可能有多条写入命令,现在只合并成了一条,从而自然就缩减了 AOF 文件的大小了。
到这里,文件大小的问题算是解决了,可是又出来新的问题了。AOF 重写是在主线程中执行的吗?如果是,那它会阻塞服务端主线程对其他请求的处理吗?如果 AOF 重写是在主线程中执行,那毫无疑问是会阻塞其他操作的。因此,Redis 在设计时,AOF 重写是放在子进程中来进行操作的,为的就是避免重写期间对主线程造成阻塞。
由于主线程不被阻塞可以正常对外提供服务,因此不可避免 AOF 重写期间还是会有新的命令写入进来。那 Redis又是怎么做到这期间的写入命令不丢失的呢?
每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程。此时,fork 会把主线程的内存拷贝一份给 bgrewriteaof 子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof 子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。在此期间,新进来的写入命令会被写入到重写日志的内存缓冲区中,以保证重写日志不丢失数据库的最新状态数据。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入到新的 AOF 文件,以此保证 AOF 记录到了数据库的最新状态。而后,我们便可以将新的AOF 文件替代旧的 AOF 文件了。
最后,既然 Redis 提供了 RDB 和 AOF 两种持久化机制,那我们到底该如何来使用呢?
如果同时开启这两种机制的话,那么在 Redis 宕机恢复的过程中,会检查是否开启了 AOF 持久化功能,如果是,则优先使用 AOF 文件来进行数据还原。只有在 AOF 持久化功能处于关闭状态时, Redis 才会通过载入 RDB 文件来还原数据库状态。
在 Redis 4.0 之后,出现了 RDB + AOF 混合机制。混合持久化机制默认情况下是关闭的,可以通过 在 redis.conf
配置文件中对 aof-use-rdb-preamble
配置项进行配置来开启。
1 | aof-use-rdb-preamble yes |
开启了混合持久化之后,RDB 内存快照以一定的频率执行,在两次快照间隙,所有的命令操作则使用 AOF 文件记录。混合持久化同样也通过 bgrewriteaof
子进程来进行。不同之处在于,bgrewriteaof
子进程优先将内存副本全量写入 AOF 文件中,然后再将重写缓冲区的增量命令继续写入到 AOF 文件中。也就是说,新的 AOF 文件是 RBD + AOF 两种格式的结合体。
当开启了混合持久化时,启动 Redis 依然优先加载 AOF 文件,AOF文件加载可能有两种情况如下:
AOF 文件开头是 RDB 的格式, 先加载 RDB 内容再加载剩余的 AOF 内容。
AOF 文件开头不是 RDB 的格式,直接以 AOF 格式加载整个文件。