redis持久化--RDB和AOF机制深入解析

1、什么是持久化(persistence)

  我们知道redis是基于内存的数据结构服务器,基于内存就意味着它的读写性能特别高,但是你在享受它的优点时就必须接受它的缺点,由于基于内存。而内存的特点是数据易失,一旦发生断电的情况就意味着数据的丢失。因此redis为我们提供了持久化的机制。所谓持久化就是将数据保存到外部存储器上,这样即使发生意外的情况,我们也可以从外部存储器的文件中恢复数据。为此redis提供了两种持久化的机制—RDB(Redis DataBase)和AOF(Append Only File)两种机制,接下来我们就来看看这两种机制的原理。

2、RDB机制

  RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。也就是说redis会按照设置的时间间隔将所有的数据全部写入磁盘当中。

  • 2.1 RDB机制的流程

  当RDB机制被触发时,会经过以下几个流程:
(1)redis父进程fork出一个子进程;
(2)这个子进程会将数据集写入到一个rdb文件的临时文件中;
(3)当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
我们用一个图示来解释:
在这里插入图片描述
  大家可能看见图中所示和描述的流程不太一样,这是得益于操作系统的copy on write(写时复制) 技术。copy on write的技术原理就是,当父进程fork子进程的时候,操作系统内核会将父进程的所有数据段的内存页(page)设置为read-only,然后子进程的地址空间指向父进程,当父进程和子进程都是只读内存的时候,没有任何事情发生。但是当其中一个进程写内存时,CPU硬件检测到内存page是read-only的,于是触发页异常中断(page-fault),陷入内核的一个中断例程。中断例程中,内核就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
  因此在rdb机制中,我们知道,在子进程写入文件的过程中,数据比较大的话是需要耗费一定的时间的,而这段时间内父进程仍然会对数据进行写入操作,因此这个时候就会触发page异常中断,这时操作系统就会使用copy On write机制使数据段分离,数据段由多个page组成的,对父进程要写操作的page,操作系统就会将这个page复制一份分离出来,然后对这个page写入,而原来的page并没有任何修改。因此子进程读取的数据永远都是在fork出来的那一刻,直到子进程终止发出信号,这个时候操作系统就会将copy的page写入原来的内存中。由于子进程写入的是临时文件,因此在写入完成之前,如果服务崩溃,那么写入都不算完成,只有在完全写入后才会替换掉旧的rdb文件。

  • 2.2 触发方式

  RDB的触发方式有两种:手动触发和自动触发。
  手动触发主要通过两个命令:save触发 ,该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止;bgsave触发 ,异步执行,该触发方式会fork一个子进程,由子进程负责持久化过程,因此阻塞只会发生在fork子进程的时候。
  根据我们的 save m n 配置规则自动触发;从节点全量复制时,主节点发送rdb文件给从节点完成复制操作,主节点会触发 bgsave;执行 debug reload 时;执行 shutdown时,如果没有开启aof,也会触发。

  • 2.3 RDB机制主要配置信息

  我们生产环境中一般不会手动去触发RDB持久化,都是通过配置文件来自动触发,以下是red默认的配置信息:

# 周期性执行条件的设置格式为
save <seconds> <changes>
# 默认的设置为:
save 900 1	// 900秒内有1条Key信息发生变化,则触发快照保存
save 300 10	// 300秒内有10条key信息变化
save 60 10000	// 60秒内有10000条key信息变化
# 以下设置方式为关闭RDB快照功能
save ""

stop-writes-on-bgsave-error: 当备份进程出错时,主进程就停止接受新的写入操作,是为了保护持久化的数据一致性问题。默认为yes
rdbcompression: 该属性将在字符串类型的数据被快照到磁盘文件时,启用LZF压缩算法。Redis官方的建议是请保持该选项设置为yes
rdbchecksum: 从RDB快照功能的version 5 版本开始,一个64位的CRC冗余校验编码会被放置在RDB文件的末尾,以便对整个RDB文件的完整性进行验证。这个功能大概会多损失10%左右的性能,但获得了更高的数据可靠性。所以如果您的Redis服务需要追求极致的性能,就可以将这个选项设置为no。
dbfilename: RDB文件在磁盘上的名称。
dir: RDB文件的存储路径。默认设置为“./”,也就是Redis服务的主目录。

3、AOF机制

  由于是周期性的同步,所以RDB存在的最大问题就是在Redis异常崩溃,需要从最近一次RDB文件恢复数据时,常常出现最近一批更新的数据丢失,而且根据快照的周期设置,这批数据的总量还可能比较大。另外,虽然使用专门的快照进程进行快照数据同步的方式,本身不会造成Redis服务出现卡顿。但如果需要快照的数据量特别大,操作系统基本上会将CPU资源用到快照操作上去,这可能间接造成包括Redis主进程在内的其它进程被挂起。所以,以一个较大的时间周期全部同步Redis数据状态的快照方式,在非常高并发的情况下并不是最好的解决方法。因此,redis提供了AOF的机制。

  • 3.1 AOF执行流程

  AOF(append only file),以日志的方式记录每次写命令,服务重启的时候重新执行AOF文件中的命令来恢复内存数据。因为解决了数据持久化实时性的问题,所以目前AOF是Redis持久化的主流方式。其流程为:
(1)所有的写命令都会追加到aof_buf(缓冲区)中。
(2)可以使用不同的策略将AOF缓冲区中的命令写到AOF文件中。
(3)随着AOF文件的越来越大,会对AOF文件进行重写。
(4)当服务器重启的时候,会加载AOF文件并执行AOF文件中的命令用于恢复数据。
简单分析一下AOF执行流程中的一些细节:

  • 因为Redis为了效率,使用单线程来响应命令,如果每次写命令都追加写硬盘的操作,那么Redis的响应速度还要取决于硬盘的IO效率,显然不现实,所以Redis将写命令先写到AOF缓冲区。
  • 写到缓冲区还有一个好处是可以采用不同的策略来实现缓冲区到硬盘的同步,可以让用户自行在安全性和性能方面做出权衡。
  • 3.2 同步的策略

  我们先来看看两个函数,一个是write函数,一个是save函数。write函数根据条件,将aof_buf中的缓存写入到AOF文件;save函数根据条件,调用fsync或fdatasync函数将AOF文件保存到磁盘。
  redis使用了三种不同的同步策略:

  • AOF_FSYNC_NO:不保存(write和read命令都由主进程执行)
  • AOF_FSYNC_EVERYSEC:每一秒钟保存一次(write由主进程完成,save由子进程完成),由于save操作由子进程来完成因此不会引起主进程的阻塞。
  • AOF_FSYNC_ALWAYS:每执行一个命令保存一次(write和read命令都由主进程执行),在这种模式下,每次执行完一个命令之后,write和save命令都会被执行。由于save命令是由Redis主进程执行的,所以在save命令执行期间,主进程会被阻塞。
    官方建议使用AOF_FSYNC_EVERYSEC的策略,它兼顾了安全性和性能。
  • 3.3 重写机制

  随着命令不断从AOF缓存中写入到AOF文件中,AOF文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制来压缩AOF文件。需要注意的是这里的压缩和rdb机制中文件的压缩不是一个概念,这里是通过优化写入命令来压缩。
  举个例子, 如果你对一个计数器调用了 100 次 INCR key , 那么仅仅是为了保存这个计数器的当前值, AOF 文件就需要使用 100 条记录(entry)。然而在实际上, 只使用一条 SET key value [EX seconds] [PX milliseconds] [NX|XX] 命令已经足以保存计数器的当前值了, 其余 99 条记录实际上都是多余的。为了处理这种情况, Redis 支持一种有趣的特性: 可以在不打断服务客户端的情况下, 对 AOF 文件进行重建(rebuild)。执行 BGREWRITEAOF 命令, Redis 将生成一个新的 AOF 文件, 这个文件包含重建当前数据集所需的最少命令。
  重写机制也有两种trigger的方式:其一是通过手动调用bgrewriteaof命令直接触发AOF重写;其二是通过配置文件,我们后面讲。
  接下来我们看看重写的流程:

  • 执行bgrewriteaof命令的时候,如果当前有进程正在执行AOF重写,那么直接返回;如果有进程正在执行bgsave,那么等待bgsave执行完毕再执行AOF重写。
  • Redis主进程会fork一个子进程执行AOF重写。
  • AOF重写过程中,不影响Redis原有的AOF过程,包括写消息到AOF缓存以及同步AOF缓存中的数据到硬盘。
  • AOF重写过程中,主进程收到的写操作还会将命令写到AOF重写缓冲区(得益于copy on write机制),注意和AOF缓冲区区分开。
  • 由于AOF重写过程中原AOF文件还在陆续写入数据,所以AOF重写子进程只会拿到fork子进程时的内存数据进行重写(重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。)。
  • 子进程拿到原内存中的数据写到一个临时的AOF文件中。
  • 子进程完成AOF重写后会发消息给主进程,主进程会把AOF重写缓冲区中的数据写到子进程生成的临时文件中,并且用新的AOF文件替换旧的AOF文件。
      这里需要注意一点,AOF重写的整个过程有三个部分会阻塞进程:主进程fork子进程的时候;主进程把AOF重写缓冲区中的数据写到AOF缓冲区的时候;使用新的AOF文件替换掉旧的AOF文件的时候。
  • 3.4 AOF主要配置信息

appendonly: 默认情况下AOF功能是关闭的,将该选项改为yes以便打开Redis的AOF功能。
appendfilename: 这个参数项很好理解了,就是AOF文件的名字。
**appendfsync:**这个参数项是AOF功能最重要的设置项之一,主要用于设置“真正执行”操作命令向AOF文件中同步的策略。appendfsync参数项可以设置三个值,分别是:always、everysec、no,默认的值为everysec。
no-appendfsync-on-rewrite: 使用no,表示使用appendfsync,表示子进程会写入到磁盘,这时候和主进程之间会有资源上的竞争,因为都要操作磁盘,所以会有阻塞的情况,但是不会丢失数据。
auto-aof-rewrite-percentage: 自动trigger重写操作的配置,auto-aof-rewrite-percentage表示如果当前AOF文件的大小超过了上次重写后AOF文件的百分之多少后,就再次开始重写AOF文件。例如该参数值的默认设置值为100,意思就是如果AOF文件的大小超过上次AOF文件重写后的1倍,就启动重写操作。
auto-aof-rewrite-min-size: 也是自动触发重写的配置,auto-aof-rewrite-min-size设置项表示启动AOF文件重写操作的AOF文件最小大小。如果AOF文件大小低于这个值,则不会触发重写操作。

4、RDB VS AOF

这里我们引用官方文档中的描述来比较两者的优缺点:https://redis.io/topics/persistence

  • RDB优点

(1)RDB 是一个非常紧凑(compact)的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份: 比如说,你可以在最近的 24 小时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。
(2)RDB 非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的数据中心,或者亚马逊 S3 中。
(3)RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
(4)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

  • RDB缺点

(1)如果你需要尽量避免在服务器故障时丢失数据,那么 RDB 不适合你。 虽然 Redis 允许你设置不同的保存点(save point)来控制保存 RDB 文件的频率, 但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。 因此你可能会至少 5 分钟才保存一次 RDB 文件。 在这种情况下, 一旦发生故障停机, 你就可能会丢失好几分钟的数据。
(2)每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。

  • AOF优点

(1)使用 AOF 持久化会让 Redis 变得非常耐久(much more durable):你可以设置不同的 fsync 策略,比如无 fsync ,每秒钟一次 fsync ,或者每次执行写入命令时 fsync 。 AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据( fsync 会在后台线程执行,所以主线程可以继续努力地处理命令请求)。
(2)AOF 文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。
(3)Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
(4)AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

  • AOF缺点

(1)对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
(2)根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。
(3)AOF 在过去曾经发生过这样的 bug : 因为个别命令的原因,导致 AOF 文件在重新载入时,无法将数据集恢复成保存时的原样。 (举个例子,阻塞命令 BRPOPLPUSH source destination timeout 就曾经引起过这样的 bug 。) 测试套件里为这种情况添加了测试: 它们会自动生成随机的、复杂的数据集, 并通过重新载入这些数据来确保一切正常。 虽然这种 bug 在 AOF 文件中并不常见, 但是对比来说, RDB 几乎是不可能出现这种 bug 的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值