目录
Redis的专栏前两篇已经讲述了基础和应用场景,这篇文章我们来说说Redis的持久化。Redis的持久化有两种方式,分别是RDB(Redis DataBase)和AOF(Append Only File),Redis默认采用的是RDB方式。
1、RDB快照
在默认情况下,Redis将内存数据库快照保存到名字为dump.rdb的二进制文件中。你可以配置持久化的策略:save N M(redis在“ N 秒内数据集至少有 M 个改动”这一条件被满足时, 自动保存一次 数据集)。
redis的策略例子:
save 900 1 -- 900秒内至少有一个改动
save 300 10 -- 300秒内至少有10个改动
save 60 10000 -- 60秒内至少有10000个改动
注:如果要关闭RDB,只要将所有的save保存策略注释掉即可,例如 #save 60 10000
除了RDB的自动保存策略,还可以手动执行命令,生成RDB快照,在redis的客户端执行save或bgsave可以生成dump.rdb文件,每次执行命令都会将redis内存数据快照到一个临时的rdb文件里,等到持久化结束就会覆盖原有的rdb快照文件。
1.1RDB的文件载入
在Redis启动的时候,由于之前的数据时保存在内存中,所以启动的一瞬间,Redis在内存中是没有数据的,如果AOF持久化功能是关闭的,那么在检测到RDB文件后,Redis就会自动加载RDB文件。
需要注意两点:
(1)因为AOF文件更新频率高于RDB文件,保存的数据比RDB文件完成,所以如果开启了AOF持久化,Redis会优先使用AOF文件来还原数据。所以只有在AOF持久化关闭的状态下,Redis才会使用RDB文件来还原数据。
(2)Redis在载入RDB文件期间,会一直处于阻塞状态,直到数据载入完成为止。
1.2写时复制(COW)机制
Redis配置自动生成rdb文件,使用的是bgsave方式。
1.3RDB可以频繁的进行快照操作吗?
通过上述讲解我们了解到,RDB在恢复数据的时候会丢失一部分未写入到磁盘的数据,那么我们是不是可以缩短快照时间间隔,因为间隔时间越小,丢失的数据越少,那么我们是否可以这样做呢?
答案是否定的,虽然bgsave执行时不占用主线程,但是这并不代表可以频繁的执行快照操作。
首先,持久化是写入磁盘的一个操作,频繁的写入磁盘,会给磁盘带来较大的压力。频繁的快照容易造成前一个快照没有写完后一个快照又开始了,这样多个快照争抢磁盘带宽,造成恶性的循环。
其次,bgsave执行时虽然不会阻塞主线程,但是fork子进程是由主进行来完成的,会阻塞主进程,fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝会消耗大量的CPU,拷贝完成之前会阻塞主进程,阻塞的时间取决于整个实例的内存大小,实例越大,内存页表越大,消耗时间越多,fork阻塞的时间也就越多;所以如果频繁的fork子进程是会对主进程有影响的。
那么有人可能会想到,是否可以做增量的快照呢?也就是下一次快照只是针对上一次快照修改、删除、增加的部分做快照。首先这个思路本身是可以的,但是增量快照需要记录哪些数据有改动,这样无疑需要增加额外的元数据来记录这些信息,对于本来就资源紧张的内存来说这并不是一个很好的方案。
那综上所述,不能进行频繁的快照,那么如何解决数据丢失的问题呢?那么就引入了Redis提供的另外一个持久化工具-AOF(append to file)。
2、AOF(append-only file)
AOF持久化是通过保存Redis执行写命令来记录数据库状态的。即每执行一个命令,就把该命令写到日志文件里,该文件是appendonly.aof。
2.1、命令执行后写日志
AOF的写日志是在每次Redis写命令执行后才执行,如下图:
这样做的好处是,首先不会阻塞当前的命令执行;其次可以避免错误的命令写入日志中,如果当前执行的Redis命令有误,那么在执行命令语法的时候就会检测到,不会再写入到aof日志文件中,进而也避免了在数据恢复时碰到错误命令。
但是也仍然存在两种问题:
(1)AOF
虽然避免了对当前命令的阻塞,但却可能会给下一个操作带来阻塞风险。因为,AOF
日志是在主进程中执行的,如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了
(2)如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。如果此时 Redis
是用作缓存,还可以从后端数据库重新读入数据进行恢复,但是,如果 Redis
是直接用作数据库的话,此时,因为命令没有记入日志,所以就无法用日志进行恢复了。
2.2、AOF缓冲区
针对上面两个问题,Redis提供了缓冲区来解决。达到避免阻塞和数据丢失的问题。
Redis在执行完命令进行持久化的时候,并不是直接写入到磁盘的日志文件中,而是先写入AOF缓冲区内,之后再通过某种策略写到磁盘。如下图:
2.3、三种写磁盘策略
Redis AOF提供了三种写磁盘的策略。
-
appendfsync always :每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
-
appendfsync everysec :每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
-
appendfsync no :从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
Redis默认的AOF策略是appendfsync everysec(每秒 fsync 一次),这种 fsync 策略可以兼顾速度和安全性。
选择了合适的回写策略,AOF持久化还有其他问题吗?AOF文件过大就是主要问题了,AOF持久化是通过保存被执行的命令,随时时间的流逝,AOF文件中的内容会越来越多,过大的AOF文件会使得追加命令变慢,而且对Redis服务器、宿主机有影响。AOF文件过大,对于使用AOF文件来进行数据还原所需的时间也相应增加。那么如何缓解这个问题呢,AOF重写机制可以做到。
2.4、AOF重写
AOF文件是追加的方式逐一记录命令的。当一个键值对被写命令多次修改,AOF文件中就会记录多条相对应的命令。如下图所示:
实际上只要将readcount设置为5这一条命令就可以了,所以AOF重写后,会将多条命令变成一条命令,这样既可以节省磁盘空间,减小AOF文件大小,而且在通过AOF数据还原时节省时间。下图为重写后的命令保存格式:
2.5、AOF重写过程
AOF重写,会使得文件变小,但是把整个数据库的数据写回磁盘,仍然是一件很耗时的事情,那么这个过程是否会导致主线程阻塞呢?下面说下AOF的重写过程。
Redis的命令执行时单线程执行的,并且有前面RDB的bgsave的讲解,我们会想到AOF的重写不会直接在主线程执行,而是fork一个子进程来执行。所以Redis会fork一个bgrewriteaof的紫禁城来完成;使用子进程可以避免使用锁的情况,保证数据的安全性,但是如何保证数据完整性和一致性呢?
例如在重写开始后主线程又接收新的命令对键值对进行修改,那么子进程是无法感知到的,这样会导致子进程重写完成后数据和原始数据库不一致的问题。
如下图:
在T6时刻服务器进程有了4个键,而子进程却只有1个键。
为了解决重写时子进程数据不一致问题,Redis设置了一个AOF重写缓冲区。如下图:
在子进程执行AOF
重写期间。服务器进程需要执行以下3个动作:
-
执行客户端命令
-
执行后追加到
AOF
缓冲区 -
执行后追加到
AOF
重写缓冲区
子进程完成AOF
重写后,它向父进程发送一个信号,父进程收到信号后会调用一个信号处理函数,该函数把AOF
重写缓冲区的命令追加到新AOF
文件中然后替换掉现有AOF
文件。父进程处理完毕后可以继续接受客户端命令调用,可以看出在AOF
后台重写过程中只有这个信号处理函数会阻塞服务器进程。
下表是完整的AOF
后台重写过程:
这样就可以保证重写日志期间的所有操作也都会写入新的AOF文件。
需要注意的是, T7 T8执行的任务会阻塞服务器处理命令。
3、RDB和AOF的选择
RDB 和 AOF ,我应该用哪一个?
在生产环境中,两种我们可以都启用,在Redis启动时如果有aof文件会优先选用aof文件进行数据的恢复,因为一般来说aof数据更全一些。
4、Redis4.0混合持久化
5、如何选择持久化方式
在技术方案选择中,我们需要平衡与取舍找到适合自己的方案。下面提供持久化方案选择的方法:
-
如果你的业务场景需要很高的性能,或者宕机之后能够尽快的恢复,而对数据完整性的要求不是那么高,那么可以采用
RDB
持久化的方式。 -
如果你的业务场景对数据完整性的要求很高,那么可以采用
AOF
的持久化方式,而至于采用那种回写策略,则取决于你对数据完整性的要求程度。 -
如果你的业务场景既要兼顾性能,又注重数据完整性,那么可以采用混合持久化的方式。
-
如果你对数据丢失无所谓,追求性能最大化的情况下,甚至可以禁用持久化。