Redis如何保证重启后的数据恢复?
Redis是内存数据库,它将自己的数据存储在内存里面,一旦Redis服务器进程退出或者运行Redis的服务器停机,Redis中的数据就会丢失。
为了避免数据丢失,所以Redis提供了持久化机制,将存储在内存中的数据保存到磁盘中,用于在Redis服务器进程退出或者运行Redis服务器的计算机停机导致数据丢失时,快速的恢复之前Redis存储在内存中的数据。
Redis提供了2种持久化方式,分别为:
- RDB持久化
- AOF持久化
RDB
RDB全名为Redis Database Backup file,意为Redis数据库备份文件,也被叫做Redis数据快照。简单来说就是把内存中的所有数据记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。正是由于RDB持久化的这种特性,所以RDB持久化也叫做快照持久化。这个快照文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时Redis中的数据。
Redis提供了两种命令来创建RDB文件。
- save
- bgsave
这两个命令有一定的区别。
save命令会直接阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何请求。
bgsave命令则会通过fork创建一个子进程,然后由子进程异步的创建RDB文件,服务器进程(父进程)继续处理命令请求。
因此我们一般更多的使用的是bgsave命令。
同时在Redis的配置文件中,有一个save参数,可以用于设定Redis自动触发bgsave的条件。
默认的配置条件表示,只要满足以下3个条件中的任意1个,BGSAVE命令就会被执行:
服务器在900s(即15分钟)之内,对数据库进行了至少1次修改
服务器在300s(即5分钟)之内,对数据库进行了至少10次修改
服务器在60s(即1分钟)之内,对数据库进行了至少10000次修改
同时也可以设定Redis的备份文件的名称
这里有一个小坑,就是如果你修改完毕dbfilename之后,你再次重启你的Redis你会发现你的数据都消失了,原因是因为Redis会去配置文件中的dbfilename中查找他的RDB文件,根据这个名称去寻找这个文件,如果找到了就从中读取数据进行数据恢复。而由于你修改了这个名称,所以你再次重启的时候Redis发现没有这个文件,因此Redis会创建这个文件,并重新开始向其中记录数据,因此你原有的数据还在dump.rdb这个文件中,只不过新的数据都会记录到新的RDB文件中。
载入RDB文件的目的是为了在Redis服务器进程重新启动之后还原之前存储在Redis中的数据。
并且Redis并没有提供从RDB文件中读取数据的命令,而是每次启动Redis他都会从当前配置文件中对应的RDB文件中进行数据读取,Redis也已经在启动Redis服务的时候告诉你这一点了。
当然,Redis服务器启动时是否会载入RDB文件还取决于服务器是否启用了AOF持久化功能,具体判断逻辑为:
- 只有在AOF持久化功能处于关闭状态时,Redis才会使用RDB文件来还原数据。
- 如果服务器开启了AOF持久化功能,那么Redis会优先使用AOF文件来还原数据。
当然,AOF持久化功能默认是关闭的,因此默认情况下Redis都是通过RDB文件来载入数据。
AOF
AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令(set,hset等)都会记录在AOF文件中,可以看作是命令日志文件。
AOF功能默认是关闭的,需要通过修改配置文件来开启。
开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中。
AOF的命令记录的频率也是可以配置的,默认为everysec,也就是每秒。
always表示每执行一次写命令都立刻记录到AOF文件中,性能影响大
everysec表示写命令执行完毕之后先放入到AOF缓冲区中,然后每隔1s将缓冲区中的文件写入到AOF中,最多丢失1s数据
no表示写命令执行完毕后先放入AOF缓冲区,由操作系统决定何时将内容写回到磁盘中,可靠性差,可能丢失大量数据
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 appendfsync everysec #每秒钟同步一次,显式地将多个写命令同步到硬盘 appendfsync no #让操作系统决定何时进行同步
AOF的日志
关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。
为什么是在执行完命令之后记录日志呢?
- 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
- 在命令执行完之后再记录,不会阻塞当前的命令执行。
这样也带来了风险(我在前面介绍 AOF 持久化的时候也提到过)
- 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
- 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。
由于AOF是记录命令,因此AOF文件会比RDB文件大得多。而且AOF会记录对同一个key的多次写操作,即使其实只有最后一次写操作有意义。通过bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到一样的效果。
例如对同一个name的操作,使用bgrewriteaof之后,本来记录三次的命令,直接会变成记录最后一次的命令。
set name 123 set name 234 set name 345 ---bgrewriteaof---- set name 345
而这些命令在AOF文件中记录的方式如下:
AOF重写机制
上面我已经说过,AOF的文件会比RDB文件大得多,所以,AOF提供了一种重写机制。
当 AOF 变得太大时,Redis 能够在后台自动重写 AOF 产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新的 AOF 文件保存的数据库状态与现有的数据库状态一致。
最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。
当然,Redis也会在触发阈值时自动去重写AOF文件,阈值也可以在redis.conf中进行配置
第一个参数表示AOF文件比上次文件增长超过百分之多少之后进行重写
第二个参数表示AOF文件体积最小多大以上才能触发重写
简而言之,Redis的AOF文件重写功能,是通过Redis服务器创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库数据相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小很多。
而AOF文件的重写是如何实现的现在来举个例子:
因为AOF文件包含了重建数据库所需的所有写命令,所以Redis服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原Redis服务器关闭之前的数据。
Redis读取AOF文件并还原数据库的详细步骤如下:
上面这个操作无非就是向一个数组添加先添加了 1 2 3,然后再出来3和2,然后又插入4 5,最终arrays中数据为1 4 5,那么我们只要lpush 5 4 1(rpush 1 4 5)即可,那么就一下子把多余的几条命令给去除了。
因为AOF文件包含了重建数据库所需的所有写命令,所以Redis服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原Redis服务器关闭之前的数据。
Redis读取AOF文件并还原数据库的详细步骤如下:
- 创建一个不带网络连接的伪客户端
因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令。
伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样。 - 从AOF文件中分析并读取出一条写命令。
- 使用伪客户端执行被读取出的写命令。
- 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被执行完毕。
如果Redis开启了AOF持久化功能,那么Redis服务器在启动的时候就会载入AOF文件。
因为AOF文件重写会进行大量的文件写入操作,所以执行这个操作的线程将被长时间阻塞。
因为Redis服务器使用单个线程来处理命令请求,所以如果由服务器进程直接执行这个操作,那么在重写AOF文件期间,服务器将无法处理客户端发送过来的命令请求。
为了避免上述问题,Redis将AOF文件重写功能放到子进程里执行,这样做有以下2个好处:
- 子进程进行AOF文件重写期间,服务器进程(父进程)可以继续处理命令请求。
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
AOF后台重写的步骤如下所示:
- 服务器进程创建子进程,子进程开始AOF文件重写
- 从创建子进程开始,服务器进程执行的所有写命令不仅要写入AOF缓冲区,还要写入AOF重写缓冲区,写入AOF缓冲区的目的是为了同步到原有的AOF文件。
写入AOF重写缓冲区的目的是因为子进程在进行AOF文件重写期间,服务器进程还在继续处理命令请求,而新的命令可能会对现有的数据库进行修改,从而使得服务器当前的数据库数据和重写后的AOF文件所保存的数据库数据不一致。这也就是上面说的在Redis7.0之前会有两份的数据。 - 子进程完成AOF重写工作,向父进程发送一个信号,父进程在接收到该信号后,会执行以下操作:
1.将AOF重写缓冲区中的所有内容写入到新AOF文件中,这样就保证了新AOF文件所保存的数据库数据和服务器当前的数据库数据是一致的。
2.对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换。
Redis提供了BGREWRITEAOF命令来执行以上步骤,如下图所示:
执行完成后,打开appendonly.aof文件,发现保存arrays键的命令从六条变为了一条:
区别
持久化方式:
RDB定时对整个内存做快照,
AOF记录每一次执行的命令
数据完整性:
RDB不完整,两次备份之间会丢失,
AOF相对完整,取决于刷盘策略
文件大小:
RDB有压缩,文件体积小,
AOF记录命令,文件体积大
宕机恢复速度:
RDB很快
AOF慢
数据恢复优先级:
RDB低,因为数据完整性不如AOF
AOF,高,因为数据完整性更高
系统资源占用:
RDB高,大量CPU和内存的消耗,
AOF低,主要是磁盘IO资源,但AOF重写时会占用大量CPU和内存资源
使用场景:
RDB使用于可以容忍数分钟的数据丢失,追求赶快的启动速度的场景。
AOF使用于对数据安全性要求较高的场景。
如何选择RDB和AOF?
关于 RDB 和 AOF 的优缺点,官网上面也给了比较详细的说明。
这里结合自己的理解简单总结一下。
RDB 比 AOF 优秀的地方 :
- RDB 文件存储的内容是经过压缩的二进制数据, 保存着某个时间点的数据集,文件很小,适合做数据的备份,灾难恢复。
AOF 文件存储的是每一次写命令,类似于 MySQL 的 binlog 日志,通常会必 RDB 文件大很多。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。不过, Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。 - 使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。
- 而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快。
AOF 比 RDB 优秀的地方 :
- RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。
生成 RDB 文件的过程是比较繁重的, 虽然 BGSAVE 子进程写入 RDB 文件的工作不会阻塞主线程,但会对机器的 CPU 资源和内存资源产生影响,严重的情况下甚至会直接把 Redis 服务干宕机。
AOF 支持秒级数据丢失(取决 fsync 策略,如果是 everysec,最多丢失 1 秒的数据),仅仅是追加命令到 AOF 文件,操作轻量。 - RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。而AOF则没有这种问题,上面他记录数据的格式也很好的证明了这一点。
- AOF 以一种易于理解和解析的格式包含所有操作的日志。你可以轻松地导出 AOF 文件进行分析,你也可以直接操作 AOF 文件来解决一些问题。比如,如果执行FLUSHALL命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。
Redis 4.0 对于持久化机制做了什么优化?
由于 RDB 和 AOF 各有优势,于是,Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。
这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。