详细分析Redis的持久化操作——RDB与AOF

时间:2020-11-26 17:48:11

一、前言

  由于疫情的原因,学校还没有开学,这也就让我有了很多的时间。趁着时间比较多,我终于可以开始学习那些之前一直想学的技术了。最近这几天开始学习Redis,买了本《Redis实战》,看到了第四章,前三章都是讲一些Redis的基本使用以及命令,第四章才开始涉及到原理相关的内容。《Redis实战》的第四章涉及到了Redis的持久化、主从复制以及事务等内容,我个人认为这些应该属于Redis中比较重要的部分,也是面试的常考内容。这篇博客就来记录一下Redis的持久化机制。

二、正文

2.1 为什么需要持久化

  学习过Redis的应该都知道,RedisMySQL等关系型数据库不同,它的数据不是存储在硬盘中,而是存放在内存,所以Redis的速度非常快。而这也就会造成一个问题:电脑如果宕机,或者由于某些原因需要重启,此时内存中的数据就会丢失Redis既然把数据存放在内存,自然也就无法避免这个问题。所以,为了在电脑重启后,能够恢复原来的数据,Redis就需要提供持久化的机制,将Redis数据库存储在内存中的数据,在磁盘中进行备份,也就是持久化。而当Redis重启后,去磁盘中将持久化的数据重新读取到内存,便能避免数据的丢失。

2.2 Redis的持久化方式

  Redis提供了两种持久化的方式,分别是:

  • 快照持久化;
  • AOF持久化;

  下面我就来详细地介绍这两种持久化的方式。

2.3 快照持久化(RDB)

  快照持久化也就做RDB持久化。快照持久化的实现方式简单来说就是:Redis将当前内存中存储的数据写入到一个文件中,将这个文件作为Redis当前的一个快照,保存在磁盘中。当Redis重启时,将这个快照文件中存储的内容加载进内存,即可恢复Redis之前的状态。默认情况下,Redis将快照保存在一个叫做dump.rdb的文件中,我们也可以在配置文件中,通过dbfilename选项来设置快照文件的名称;默认情况下快照文件就保存在Redis的安装目录下,我们可以在配置文件中,通过dir选项来配置快照文件的存储路径(其实也是AOF的路径)。

2.4 执行快照持久化的方式

  执行快照持久化有两种方式:

2.4.1 使用配置文件

  第一种方式就是在Redis的配置文件中(windows下这个文件叫redis.windows-service.conf)加上save配置项,比如像下面这样:

save 60 100:在配置文件中加上这一条的意思是,Redis会每60秒检查一次,若在这60秒中,Redis数据库执行了100次以上的写操作,那Redis就会生成一个快照文件,替换原来的快照文件;若不满足这个条件,则不生成快照,继续等待60秒;

  我们可以根据自己的需求,调整每次等待的时间,以及对写操作次数的要求。而且,我们可以在配置文件中,添加多个save选项,比如一个save 60 100,一个save 5 10,则Redis5秒以及每100秒都会判断一次。需要注意的是,我们不应该让生成快照太过频繁,因为这是一个比较消耗资源的工作,会降低Redis的响应速度。

2.4.2 使用指令

  执行快照持久化的第二个方法就是使用Redis指令,Redis提供了两个指令来请求服务器进行快照持久化,这两个指令分别是SAVEBGSAVE

(a)BGSAVE指令

  BGSAVE的执行流程如下:

  1. Redis调用系统的fork(),创建出一个子进程;
  2. 子进程将当前Redis中的数据,写入到一个临时文件中;同时父进程不受影响,继续执行客户端的请求;
  3. 子进程将所有的数据写入到了临时文件后,于是使用这个文件替换原来的快照文件(默认是dump.rdb);

  值得一提的是,通过配置文件执行快照持久化的方式,实际上就是Redis在判断满足条件时,调用BGSAVE指令来实现的。

(b)SAVE指令

  SAVE指令生成快照的方式与BGSAVE不同,Redis执行SAVE指令时,不会创建一个子进程,异步的生成快照文件,而是直接使用Redis当前进程。执行SAVE指令在创建快照的过程中,Redis服务器会阻塞所有的Redis客户端,直到快照生成完毕,并更新到磁盘之后,才会继续执行客户端发来的增删改查的指令。

  当然,这个指令一般很少使用,因为会阻塞客户端,造成停顿。但是实际上,这个指令的执行效率一般比BGSAVE更高,因为不需要创建子进程,而且在这个过程中,其他操作被阻塞,Redis服务器一心一意地生成快照。在《Redis实战》中,作者提到了使用SAVE指令的一个案例:

  当前服务器的Redis数据库中保存了大量数据,使用BGSAVE指令生成快照会非常的耗时 ,而且由于所剩内存不多,甚至无法创建子进程,于是作者编写了一个shell脚本,这个脚本的内容就是让服务器每天凌晨3点,执行SAVE命令,生成快照,这样就不需要创建子进程,而且由于是凌晨三点,用户较少,SAVE的阻塞机制也不会有太大的影响。

2.5 快照持久化的优缺点

(1)优点:

  1. 快照的rdb文件是一个经过压缩的紧凑文件,它保存了Redis在某个时间点上的数据集,这个文件非常适合用来备份。我们可以存储Redis服务器在不同时间点上的rbd文件,比如说一小时存储一次,一个月存储一次,这样就可以在遇到问题或有特殊需求时,将Redis恢复到某一个时间点;

  2. RDB非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的服务器上;

  3. RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘I/O操作,所以不会影响父进程处理客户端的请求;

  4. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

(2)缺点

  1. 当我们的服务器发生异常,导致停机时,那我们将会丢失最后一次创建快照后,所作的所有写操作,因为这些操作发生在内存中,还没来得及同步到磁盘。比如我们在配置文件中配置每5分钟生成一次快照,那当系统发生故障导致宕机时,我们将会丢失好几分钟内的操作;
  2. 每次执行BGSAVE创建快照,都需要先创建出一个子进程,再由子进程执行后续操作,当内存中数据较大时,创建一个子进程将会非常耗时,造成服务器等待数毫秒,甚至达到一秒,影响用户体验。而且从这一点也可以说明,我们不能通过提高生成快照的频率,来解决第一个缺点;

2.6 AOF持久化

  AOF全称为只追加文件(append-only file),它的实现方式简单来说就是:AOF持久化机制,会将Redis执行的所有写指令,追加到到AOF的末尾,当服务器发生宕机,或者由于某些原因需要重启时,在重启后,读取AOF文件,重新执行其中记录的写操作,以此达到恢复数据的目的。

  AOF持久化机制默认是关闭的,我们可以在配置文件中,配置appendonly yes来开启。同时我们也可以通过配置appendfsync,控制写操作追加到AOF中的频率,它有如下三种选项:

  • alwaysRedis每次执行写指令,都会立即将这个写指令同步到AOF中;使用这个选项时,Redis发生异常,则最多只会丢失一次写操作(也就是在同步的过程中宕机,没同步完成),但是这也会导致Redis的响应速度变慢,因为此选项会造成频繁的IO操作;
  • everysec(默认):每秒同步一次,使用此选项,速度足够快,而且发生宕机时也只会丢失1s内的写操作;
  • no:让操作系统来决定什么时候进行同步,这个选项速度更快,但是不安全,宕机时丢失数据的多少是不确定的;

  推荐(也是默认),使用第二个选项everysec,每秒进行一次同步,因为这个选项兼顾了速度与安全性,而第一个选项太慢,第三个选项无法保证安全性。

2.7 AOF的重写机制

  AOF持久化的执行机制就是,不断地将写指令追加到AOF的末尾,这样就会导致AOF越来越大。为了解决AOF越来越大的问题,Redis实现了一种优化机制——AOF重写。当Redis检查到AOF已经很大时,就会触发重写机制,优化其中的内容,将它优化为能够得到相同结果的最小的指令集合。

  比如说,我们在Redis中使用SET指令创建了一个String类型的数据,最后使用DEL指令将它删除了。如果开启了AOF持久化,那么则AOF中,将会记录这条SETDEL指令。但是,在执行重写的过程中,这个String最后被删除了,那么Redis就不会将这两条指令加入新的AOF中,因为已经被删除的数据,不需要恢复。再比如说,我们使用Redis维护了一个计数器cnt,我们使用了100INCR指令,让cnt自增到了100,而Redis重写AOF时,可以将这100incr修改为一条SET指令,直接将cnt设置为100,而不是保留100INCR

  经过了重写后,AOF的大小将会大大减小,而且也去除了不必要的操作,优化了恢复数据的指令集。而AOF重写的过程与生成一个快照文件类似,如下:

  1. Redis执行fock(),创建一个子进程用来执行后续操作,而父进程继续处理发送到Redis的执行请求;
  2. 子进程重写旧AOF文件,将重写后的内容写入到一个临时文件;
  3. 如果这个过程中,有新的写指令到达,那么Redis会将这些写指令依旧追加到旧的AOF中,同时也会将这些指令加入到内存的一个缓冲区中。这样做的目的是,如果服务器发生异常,AOF重写失败,这些指令依然能够保存在旧AOF,不会丢失;
  4. 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有写指令追加到新 AOF 文件的末尾;
  5. 使用新AOF替换旧的AOF,这之后执行的所有写指令都将追加到新的AOF中;

2.8 AOF的错误处理

  AOF文件是有可能发生错误的,比如上面提过,如果当前Redis正在向AOF中追加一个写指令,但是此时服务器宕机,那么这个存入AOF中的这个写指令就是不完整的,也就是AOF出现了错误。如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。

  那么,当AOF发生了错误,应该如何处理呢?我们可以使用如下命令:

redis-check-aof --fix:redis-check-aof用来检测AOF是否存在错误,如果指定了--fix参数,那么Redis在检测到AOF的错误后,会对AOF进行修复。

  redis-check-aof修复AOF的方式非常简单:扫描AOF文件,寻找其中不正确或不完整的指令,当发现第一个出错的指令后,就将这个指令以及这之后的所有指令删除。为什么需要删除出错指令之后的所有指令呢?因为当一条指令出错,很有可能影响到后续的操作,导致后续操作的都是脏数据,而Redis无法检测哪些指令是受到影响的,所以为了保险起见,就将后续指令全部删除。不过不用担心,因为在大多数情况下,出错的都是AOF最末尾的指令。

2.9 AOF的优缺点

(1)优点:

  1. 使用 AOF 持久化会让 Redis 变得非常耐久,意思就是说,当发生异常导致需要重启服务器时,只会丢失很少的一部分数据,因为AOF持久化默认1s同步一次,也就是说,Redis最多只会丢失1s中所做的修改;
  2. Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。
  3. AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。
  4. AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。

(2)缺点:

  1. 对于相同的数据集来说,AOF 文件的体积通常要大于快照RDB文件的体积大,因为RDB只存储数据,而AOF中的写指令,既包含指令,也包含数据;
  2. 如果AOF体积太大,那么恢复数据将要花费较长的时间,因为需要重做指令;

2.10 选择快照还是AOF?

  说到这里,可能就有人要问了,在实际生产中,我们应该使用快照持久化还是AOF持久化呢?

1、如果希望自己的数据库有很高的安全性,则应该两者同时使用,AOF用作精确记录,而快照用作数据备份,前面也说过,快照非常适合用来做数据备份,因为它只存储数据库中的数据,并且经过了压缩,而且它恢复Redis数据的速度一般要快于AOF

2、如果可以容忍一段时间内的数据丢失,则可以考虑只使用快照持久化,减小服务器的开销;

  值得一提的是,如果我们同时开启了快照持久化和AOF持久化,Redis在重启后,会优先选择AOF来恢复数据,因为一般情况下,AOF能够更加完整地恢复数据。

三、总结

  快照持久化和AOF持久化各有优劣,在实际生产环境中,我们一般是两者配合使用,快照持久化消耗较低,而且适合用于备份,但是会丢失一段时间的数据;AOF持久化更加地耐久,可靠性更高,但是开销可能相对较高。以上就对Redis的持久化机制做了一个比较详细的介绍,相信看完只后,对Redis会有一个更加深入的理解。

四、参考