Redis之持久化讲解

文章目录
- 1 Redis持久化
-
- 1.1 持久化流程
- 1.2 RDB机制
-
- 1.2.1 save触发方式
- 1.2.2 bgsave触发方式
- 1.2.3 自动触发
- 1.2.4 RDB的优势和劣势
-
- 1.2.4.1 优势
- 1.2.4.2 劣势
- 1.3 AOF机制
-
- 1.3.1 写后日志优势和风险
- 1.3.2 AOF三种触发机制
- 1.3.3 文件重写
-
- 1.3.3.1 重写的作用
- 1.3.3.2 重写的过程
- 1.3.3.3 总结
- 1.3.4 优缺点
-
- 1.3.4.1 优点
- 1.3.4.2 缺点
- 1.4 两者混合
1 Redis持久化
Redis数据是存储在内存中的,但是我们都知道内存的数据变化是很快的,也容易发生丢失,为了保证Redis数据不丢失,那就要把数据从内存存储到磁盘上,以便在服务器重启后还能够从磁盘中恢复原有数据,这就是Redis的数据持久化。
Redis数据持久化有三种方式:
AOF日志(Append Only File,文件追加方式):记录所有的操作命令,并以文本的形式追加到文件中。RDB快照(Redis DataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘(早期默认方式)。- 混合持久化方式:
Redis 4.0新增了混合持久化的方式,集成了RDB和AOF的优点
1.1 持久化流程
既然redis的数据可以保存在磁盘上,那么这个流程是什么样的呢?
要有下面五个过程:
- 客户端向服务端发送写操作(数据在客户端的内存中)
- 数据库服务端接收到写请求的数据(数据在服务端的内存中)
- 服务端调用
write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中) - 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)
- 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)
1.2 RDB机制
RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是 默认的持久化方式(早期),这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
RDB采用的是内存快照的方式,它记录的是某一时刻的数据,而不是操作,所以采用RDB方法做故障恢复时只需要直接把RDB文件读入内存即可,实现快速恢复。
在我们安装了redis之后,所有的配置都是在redis.conf文件中,里面保存了RDB和AOF两种持久化机制的各种配置
既然RDB机制是通过把某个时刻的所有数据生成一个快照来保存,那么就应该有一种触发机制,是实现这个过程。对于RDB来说,提供了三种机制:save、bgsave、自动化。我们分别来看一下
1.2.1 save触发方式
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。具体流程如下:

执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取
1.2.2 bgsave触发方式
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下:

具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上Redis内部所有的RDB操作都是采用bgsave命令即默认配置
在执行快照的同时,Redis 会借助操作系统提供的写时复制技术(Copy-On-Write, COW),正常处理写操作。bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件
Redis是怎么解决在bgsave做快照的时候允许数据修改呢?
这里主要是利用bgsave的子线程实现的,具体操作如下:
如果主线程执行读操作,则主线程和 bgsave 子进程互相不影响;
如果主线程执行写操作,则被修改的数据会复制一份副本,然后,主线程在这个数据副本上进行修改。同时,bgsave 子进程可以继续把原来的数据 写入 RDB 文件,在这个过程中,主线程仍然可以直接修改原来的数据

虽然 bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量快照,也会带来两方面的开销:
- 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环(所以,在
Redis中如果有一个bgsave在运行,就不会再启动第二个bgsave子进程) - 另一方面,
bgsave子进程需要通过fork操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork这个创建过程本身会阻塞主线程 ,而且主线程的内存越大,阻塞时间越长。
需要注意:Redis 对 RDB 的执行频率非常重要,因为这会影响快照数据的完整性以及 Redis 的稳定性,所以在 Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制: 把数据以 RDB 的方式写入文件,再将后续的操作命令以 AOF 的格式存入文件,既保证了 Redis 重启速度,又降低数据丢失风险,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作
1.2.3 自动触发
自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置,我们可以去设置:
save:这里是用来配置触发Redis的RDB持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如save m n。表示m秒内数据集存在n次修改时,自动触发bgsave。
默认如下配置:
表示900秒内如果至少有1个key的值变化,则保存save 900 1
表示300秒内如果至少有10个key的值变化,则保存save 300 10
表示60秒内如果至少有10000个key的值变化,则保存save 60 10000
不需要持久化,那么你可以注释掉所有的save行来停用保存功能。stop-writes-on-bgsave-error:默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了rdbcompression:默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。rdbchecksum:默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。dbfilename:设置快照的文件名,默认是dump.rdbdir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名
1.2.4 RDB的优势和劣势
1.2.4.1 优势
RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
RDB在恢复大数据集时的速度比 AOF 的恢复速度要快。
1.2.4.2 劣势
RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
需要注意,Redis 对 RDB 的执行频率非常重要,因为这会影响快照数据的完整性以及 Redis 的稳定性,所以在 Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制: 把数据以 RDB 的方式写入文件,再将后续的操作命令以 AOF 的格式存入文件,既保证了 Redis 重启速度,又降低数据丢失风险
1.3 AOF机制
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录
AOF采用的是写后日志的方式,Redis先执行命令把数据写入内存,然后再记录日志到文件中。AOF日志记录的是操作命令,不是实际的数据,如果采用AOF方法做故障恢复时需要将全量日志都执行一遍

AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。
我们以 Redis 收到set testkey testvalue命令后记录的日志为例,看看 AOF 日志的内容。其中,*3 表示当前命令有三个部分,每部分都是由 $+数字 开头,后面紧跟着具体的命令、键或值。这里,数字表示这部分中的命令、键或值一共有多少字节。例如,$3 set 表示这部分有 3 个字节,也就是set命令。

1.3.1 写后日志优势和风险
AOF采用的是写后日志的方式,我们平时用的MySQL则采用的是 写前日志,那 Redis为什么要先执行命令,再把数据写入日志呢?
这个主要是由于Redis在写入日志之前,不对命令进行语法检查,所以只记录执行成功的命令,避免出现记录错误命令的情况,而且在命令执行后再写日志不会阻塞当前的写操作
后写日志的风险:
- 数据可能会丢失:如果
Redis刚执行完命令,此时发生故障宕机,会导致这条命令存在丢失的风险- 如果此时
Redis是用作缓存,还可以从后端数据库重新读入数据进行恢复。 - 如果
Redis是直接用作数据库的话,此时,因为命令没有记入日志,所以就无法用日志进行恢复了。
- 如果此时
- 可能阻塞其他操作:
AOF日志其实也是在主线程中执行,所以当Redis把日志文件写入磁盘的时候,还是会阻塞后续的操作无法执行,(即:AOF虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险)
AOF日志也是在主线程中执行(写回策略为always时),如果在把日志文件写入磁盘时,磁盘写压力大,就会导致写盘很慢,进而导致后续的操作也无法执行了。
1.3.2 AOF三种触发机制
- 每修改同步
always:每个写命令执行完,立马同步地将日志写回磁盘,同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好 - 每秒同步
everysec:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘
异步操作,每秒记录,如果一秒内宕机,有数据丢失, - 不同步
no:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
| 配置项 | 写回时机 | 优点 | 缺点 |
|---|---|---|---|
| Always | 同步写回 | 可靠性高,数据基本不丢失 | 每个写命令都有落盘,性能影响较大 |
| Everysec | 每秒写回 | 性能适中 | 宕机时丢失1秒内的数据 |
| No | 操作系统控制的写回 | 性能好 | 宕机时丢失数据较多 |
我们就可以根据系统对高性能和高可靠性的要求,来选择使用哪种写回策略了。
- 想要获得高性能,就选择
No策略; - 想要得到高可靠性保证,就选择
Always策略; - 允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择
Everysec策略
1.3.3 文件重写
1.3.3.1 重写的作用
AOF 是以文件的形式在记录接收到的所有写命令。随着接收的写命令越来越多,AOF 文件会越来越大。这也就意味着,我们一定要小心 AOF 文件过大带来的性能问题,主要在于以下三个方面:
- 文件系统本身对文件大小有限制,无法保存过大的文件;
- 如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
- 如果发生宕机,
AOF中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到Redis的正常使用。
AOF 重写机制就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件,也就是说,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入 。重写机制具有多变一功能。所谓的多变一,也就是说,旧日志文件中的多条命令,在重写后的新日志中变成了一条命令。

1.3.3.2 重写的过程
AOF 日志由主线程写回不同,重写过程是由 后台子进程 bgrewriteaof 来完成的,这也是为了避免阻塞主线程 ,导致数据库性能下降。
可以把重写的过程总结为 一个拷贝,两处日志 :
一个拷贝:就是指,每次执行重写时,主线程fork出后台的bgrewriteaof子进程。此时,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。第一处日志:指的是因为主线程未阻塞,仍然可以处理新来的操作,Redis会把这个操作写到它的缓冲区。这样一来,即使宕机了,这个AOF日志的操作仍然是齐全的,可以用于恢复。第二处日志:就是指新的AOF重写日志。这个操作也会被写到重写日志的缓冲区。这样,重写日志也不会丢失最新的操作。等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的AOF文件,以保证数据库最新状态的记录。
此时,我们就可以用新的 AOF 文件替代旧文件了。

为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写。

1.3.3.3 总结
总结来说,每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用子进程进行日志重写,所以,这个过程并不会阻塞主线程 。
正因为记录的是操作命令,而不是实际的数据,所以,用 AOF 方法进行故障恢复的时候,需要逐一把操作日志都执行一遍。如果操作日志非常多,Redis 就会恢复得很缓慢,影响到正常使用
1.3.4 优缺点
1.3.4.1 优点
AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据
1.3.4.2 缺点
- 对于同一份数据来说,
AOF日志文件通常比RDB数据快照文件更大 AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的- 以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来
1.4 两者混合
虽然 bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量快照,也会带来两方面的开销。
- 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环(所以,在
Redis中如果有一个bgsave在运行,就不会再启动第二个bgsave子进程) - 另一方面,
bgsave子进程需要通过fork操作从主线程创建出来。虽然,子进程在创建后不会再阻塞主线程,但是,fork这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。
RDB和AOF到底该如何选择
选择的话,两者加一起才更好。因为两个持久化机制你明白了,剩下的就是看自己的需求了,需求不同选择的也不一定,但是通常都是结合使用。有一张图可供总结:

需要注意:Redis 对 RDB 的执行频率非常重要,因为这会影响快照数据的完整性以及 Redis 的稳定性,所以在 Redis 4.0 后,增加了 AOF 和 RDB 混合的数据持久化机制,简单来说:内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作,这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销



