redis之持久化

Redis的持久化主要包括RDB和AOF两种方式。RDB通过快照保存数据库状态,适合全量备份,恢复速度快;AOF记录每次写操作,保证数据安全性,支持秒级持久化。AOF重写可以优化文件体积,避免数据冗余。混合持久化结合RDB和AOF优点,平衡数据安全和恢复速度。
摘要由CSDN通过智能技术生成

什么是数据库状态

redis是一个键值对类型的数据库服务器,服务器中通常包含任意个非空的数据库,而每个数据库又可以包含任意个键值对,为了方便起见,我们将服务器中的非空数据库以及它们的键值对统称为数据库状态

Redis持久化分类

redis的持久化分为两类:RDB持久化AOF持久化。但在使用时会有三种使用方式:RDB持久化方式AOF持久化方式RDB&AOF混合持久化方式

RDB持久化方式

RDB持久化的逻辑

RDB持久化即可以手动执行也可以根据服务器配置选项定期的执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中,这个文件其实是一个二进制的文件,通过这个文件可以还原到生成RDB文件时的数据库状态。

RDB文件的创建

有两个生成redis的RDB文件的命令,一个是SAVE,一个是BGSAVE。SAVE命令是使用服务器进程来生成RDB文件,在生成RDB文件的时候,会阻塞所有的读写操作,服务器不能处理任何命令请求。BGSAVE命令是通过服务器进程派生出来一个子进程,然后由子进程负责创建RBD文件,服务器进程可以继续处理命令请求。
其实创建RDB文件的工作都是通过rdbsave函数实现的,只不过上面两种命令以不同的方式调用这个函数。

  • SAVE命令执行时的服务器状态
    我们上面已经提到过SAVE命令会阻塞一切请求,当然也包括BGSAVE请求,所以在执行SAVE期间,再次执行BGSAVE命令会被直接拒绝。
  • BGSAVE命令执行时的服务器状态
    我们知道BGSAVE命令是服务器的子进程来完成的,服务器进程可以继续接收和执行命令,但是在BGSAVE命令执行期间服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的方式和平时有所不同,主要有以下几种情形:
      1.在BGSAVE命令执行期间,客户端发起的SAVE命令会被服务器拒绝。服务器禁止SAVE命令和BGDAVE命令同时执行是为了避免父进程和子进程同时执行两个rdbsave调用,防止产生竞争条件。
      2.在BGSAVE命令执行期间客户端发起BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件
      3.在BGSAVE命令执行期间,客户端发起BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行
      4.在BGREWRITEAOF命令执行期间,客户端发起BGSAVE命令会被服务器直接拒绝,主要是因为BGREWRITEAOF和BGSAVE命令都是由子进程来完成的,禁止它们同时执行只是一个性能方面的考虑,因为并发两个子进程,并且这两个子进程同时进行大量的磁盘写入操作,并不是什么好事情。

RDB文件的载入

对于RDB文件的载入就相对简单了,RDB文件的载入工作是在服务器启动的时候自动执行的,并且在服务器载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

RDB自动间歇性保存

上面我们讲了如何创建RDB文件,但是如何间歇性创建RBD文件呢?
有两种方式创建RDB文件:一是手动的执行命令,二是通过配置让服务器自动来创建RDB文件。我们可以在redis的配置文件中通过以下配置来实现:

save    900    1
save    300    10
save     60     10000

用户可以通过save选项设置多个保存条件(可以超过三个了,十个八个都可以,因为这些保存条件其实是保存在一个数组中的),但是只要其实任意一个条件满足,服务器就会执行BGSAVE命令。而上面的三条保存配置其实也是在我们开启了RDB持久化但是没有配置相关保存条件下时服务器给的默认的配置。

如何自动触发生成RDB文件的配置条件的呢?

其实服务器除了通过一个数组存储我们的保存条件外,还会维持一个dirty的计数器,以及一个lastsave属性,其中dirty计数器用来统计距离上一次成功执行SAVE命令或者BGSAVE命令后服务器对数据库状态进行了多少次修改(包括写入,删除,更新等操作),而lastsave属性是一个UNIX的时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。这两个值都会在成功执行完BGSAVE命令后重置:dirty重置成0,lastsave更新为当前时间。
而对于条件的判断则是Redis服务器会周期性的操作函数serverCron默认每隔100毫秒执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否满足,如果满足则执行BGSAVE命令。必须同时满足如下条件

  • 距离上一次成功执行保存的时间超过设置的时间
  • 数据库状态的修改次数超过设置的修改次数

RDB的文件结构

暂略

AOF持久化方式

AOF持久化的逻辑

与RBD持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存redis服务器所执行的写命令来记录数据库状态的。
被写入到AOF文件的命令都是以Redis的命令请求协议格式保存的,因为Redis的命令请求协议是纯文本的,所以我们可以直接打开一个AOF文件,观察里面的内容,里面保存的基本都是我们执行的命令,但是会有一些SELECT命令,SELECT命令是用于指定数据库的,此命令是服务器自动添加的。

AOF持久化的过程

AOF持久化过程的实现可以分为命令追加文件写入文件同步三个步骤。

  • 命令追加:当AOF功能打开的时候,服务器执行一条命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾,但是aof_buf缓冲区的命令什么时候写入aof文件要根据我们的appendfsync的相关配置来决定
  • 文件写入和同步:为了提高文件的写入效率,在现代的操作系统中,当用户调用write函数,将一些数据写入到文件时,操作系统通常会将写入数据暂时存放到一个内存缓冲区里面,此时并没有真正的把数据持久化到磁盘上,需要等等到缓冲区满了或者超过了执行的时限之后,才调用fsync函数真正的将缓冲区中的数据写入到磁盘里。这样做虽然提高了效率,但也为写入数据都带来了安全问题,因为如果计算机发生停机,那么保存在操作系统内存缓冲区里的数据将会丢失。简单的理解,写入操作其实只是将数据写入到了操作系统的内存缓冲区;而同步操作才真正的将数据写入到磁盘文件中,同步操作(fsync函数的调用)的执行频率可以通过配置控制,下面会详细讲到。

我们需要注意的是,Redis的服务器进程就是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样的需要定时运行的函数。
因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。flushAppendOnlyFile函数的行为是由服务器配置文件的appendfsync选项的值来决定的,appendfsync的值有三种:

  • always
    服务器在每个事件循环都要将aof_buf缓冲区中的所有数据写入到AOF文件,并且同步AOF文件,所以效率是appendfsync三个选项中最低的,但从安全性来说,这种设置是最安全的,即便机器出现故障,也只是会丢失一个事件循环中所产生的命令数据
  • everysec
    将aof_buf缓冲区的所有内容写入到AOF文件(此时并没有同步达到磁盘上),如果上次同步AOF文件的时间距离现在超过1s,那么再次对AOF文件进行同步,并且这个同步操作有一个线程专门完成。这种设置,服务器在每个事件循环中都要将aof_buf缓冲区中的所有内容写入到Aof文件,并且每隔1s就要在子线程中对AOF文件进行一次同步。从效率上来讲everysec模式足够快,并且就算出现故障停机,数据库也只丢失一秒钟的命令数据。
  • no
    在服务器的每个事件循环中都要将aof缓冲区中的所有内容写入到AOF文件,但不对AOF文件进行同步,何时同步由操作系统来决定,所以这种设置是如果服务器停机,将会丢掉上次AOF文件同步后的所有写命令数据。

AOF文件的载入和数据还原

因为AOF文件中包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据状态。

AOF重写

为什么要进行AOF重写能?

因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着时间的流逝,AOF文件内容会越来越多,文件体积也会越来越大,如果不加以控制的话,体积过大的AOF文件很可能对Redis服务器甚至宿主机造成影响,并且AOF文件的体积越大,在使用AOF文件来进行数据还原所需的时间就越长。对于数据恢复来说,AOF文件中会有很多冗余的命令,比如对某个key操作了八次,AOF文件中会记录八条写命令,但其实我们只关心最后一次命令执行后的数据状态,为了解决AOF文件体积膨胀问题,Redis提供了AOF文件重写功能

什么是AOF重写?

将生成新的AOF文件替换旧的AOF文件的功能叫做AOF文件重写,但是实际上AOF文件重写并没有对旧的AOF文件进行任何读取、分析或者写入的操作,只是对AOF重写时候的数据状态转成写命令保存起来,也就是说重写后的AOF文件中保存着能够还原重写时数据库状态所必须的命令,这样就可以大大的减小文件的体积了。

AOF重写的触发方式
  • 通过配置自动触发
    要想使用自动触发方式进行AOF重写,需要进行类似如下:
auto-aof-rewrite-percentage   100
auto-aof-rewrite-min-size        64mb

假如做了如上配置,其释义如下。
当AOF文件大于64MB时,并且AOF文件当前大小比基准大小增长了100%时会触发一次AOF重写。那么基准大小如何确定呢?
起始的基准大小为Redis重启并加载完AOF文件之后,aof-buf的大小。当执行完一次AOF重写之后,基准大小相应更新为重写之后AOF文件的大小。

  • 手动执行bgrewriteaof命令显示触发
    通过客户端输入bgrewriteaof命令,该命令调用bgrewriteaofCommand,然后创建管道,fork进程,子进程调用rewriteAppendOnlyFile执行AOF重写操作,父进程记录一些统计指标后继续进入主循环处理客户端请求。当子进程执行完毕后,父进程调用回调函数做一些后续的处理操作。
    我们知道RDB保存的是一个时间点的快照,但是AOF故障时最少可以只丢失一条命令。子进程执行重写时可能会有成千上万条命令继续在父进程中执行,那么如何保证重写完成后的文件也包括这些命令呢?
    首先需要在父进程中将重写过程中执行的命令进行保存,其次需要将这些命令在重写后的文件中进行回放。Redis为了尽量减少主进程的阻塞时间,通过管道按批次将父进程累积的命令发送给子进程,由子进程重写完成后进行回放。因此子进程退出后只会有少量的命令还累积在父进程中,父进程只需回放这些命令即可。
AOF重写的过程

在了解AOF重写过程之前,我们先了解一个概念:AOF重写缓冲区(aof_rewrite_buf_blocks),这是一个list类型的缓冲区,每个节点中保存一个aofrwblock类型的数据,该结构体中会保存10MB大小的缓冲区内容,并且有缓冲区使用和空闲长度的记录。当一个节点缓冲区写满之后,会开辟一个新的节点继续保存执行过的命令。

因为Reids是单线程的,为了不使AOF重写时阻塞服务器的正常运行,redis决定将AOF重写放到一个子进程中进行,这样做有两个好处:

  • 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求
  • 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据得安全性

我们要注意,在子进程进行AOF重写期间,父进程还需要继续处理命令请求,Redis用AOF重写缓冲区来保存AOF重写期间执行的命令,由上可知,在子进程AOF重写期间,服务器进程会进行三个工作:

  • 执行客户端发来得命令
  • 将执行后的写命令追加得AOF缓冲区
  • 将执行后得写命令追加得AOF重写缓冲区

这样做的好处是可以保证:

  • .AOF缓冲区的内容会定期被写入和同步到AOF文件,对现在AOF文件的处理工作会如常进行
  • 从创建子进程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区里面

当子进程完成AOF重写后,会向服务器进程发一个信号,并调用一个信号处理函数执行以下工作:

  • 将AOF重写缓冲区中的所有内容写入到新得AOF文件中,这时新AOF文件所保存得数据库状态将和服务器当前得数据库状态保持一致
  • 对新的AOF文件进行改名,原子的覆盖现在的AOF文件,完成新旧文件的交替

在整个AOF后台重写过程中,只有信号处理的时候会对服务器进程(父进程)造成阻塞,其他时候AOF后台重写都不会阻塞父进程,这将AOF重写对服务器造成的影响降到了最低。

需要注意的是:在重写程序中处理列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查包含元素的数量,如果数量超过了设置的常量值(这个值不同版本是不一样的),就会用多条命令来记录这个键,而不单单使用一条命令。

AOF&RDB混合持久化方式

混合持久化指进行AOF重写时子进程将当前时间点的数据快照保存为RDB文件格式,而后将父进程累积命令保存为AOF格式。

加载时,首先会识别AOF文件是否以REDIS字符串开头,如果是,就按RDB格式加载,加载完RDB后继续按AOF格式加载剩余部分。

开启混合持久化的配置:

aof-user-rdb-preamble yes

RDB持久化和AOF持久化的对比

RDB优劣势

  • 优势
    RDB只代表某个时间点上的数据快照,所以适用于备份与全量复制,如一天进行备份一次。
    Redis在加载RDB文件恢复数据远快于AOF文件
    性能上考虑RDB优于AOF,因为我们保存RDB文件只需fork一次子进程进行保存操作,父进程没有对磁盘I/O
  • 劣势
    RDB没办法做到实时的持久化数据,因为fork是重量级别的操作,频繁执行成本过高。
    RDB需要经常fork子进程来保存数据集到磁盘,当数据集比较大的时候,fork的过程是比较耗时的,可能会导致redis在一些毫秒级不能响应客服端请求。
    老版本的Redis无法兼容新版本的RDB文件。

AOF优劣势

  • 优势
    通过配置同步策略基本能够达到实时持久化数据,如配置为everysec,则每秒同步一次AOF文件,也就是说最多丢失一秒钟的数据,兼顾了性能与数据的安全性
    AOF文件是一个只进行追加操作的日志文件(append only log), 因此对 AOF 文件的写入不需要进行 seek ,即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。
  • 劣势
    对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
    与AOF相比,在恢复大的数据的时候,RDB方式更快一些。

问题思考

问题一:既然已经有了RDB为什么还需要AOF?
这需要从RDB和AOF的实现方式考虑:RDB保存的是一个时间点的快照,那么如果Redis出现了故障,丢失的就是从最后一次RDB执行的时间点到故障发生的时间间隔之内产生的数据。如果Redis数据量很大,QPS很高,那么执行一次RDB需要的时间会相应增加,发生故障时丢失的数据也会增多。
而AOF保存的是一条条命令,理论上可以做到发生故障时只丢失一条命令。但由于操作系统中执行写文件操作代价很大,Redis提供了配置参数,通过对安全性和性能的折中,可以设置不同的策略。

问题二:既然AOF的数据安全性更高,是否可以只使用AOF呢?为什么Redis推荐RDB和AOF同时开启呢?
RDB保存的是最终的数据,是一个最终状态,而AOF保存的是达到这个最终状态的过程。很明显,如果Redis有大量的修改操作,RDB中一个数据的最终态可能会需要大量的命令才能达到,这会造成AOF文件过大并且加载时速度过慢(Redis提供了一种AOF重写的策略来解决上述问题,后文会详细描述其实现原理)。
再来考虑一下AOF和RDB文件的加载过程。RDB只需要把相应数据加载到内存并生成相应的数据结构(有些结构如intset、ziplist,保存时直接按字符串保存,所以加载时速度会更快),而AOF文件的加载需要先创建一个伪客户端,然后把命令一条条发送给Redis服务端,服务端再完整执行一遍相应的命令。根据Redis作者做的测试,RDB10s~20s能加载1GB的文件,AOF的速度是RDB速度的一半(如果做了AOF重写会加快)。因为AOF和RDB各有优缺点,因此Redis一般会同时开启AOF和RDB。

假设线上同时配置了RDB和AOF,那么会带来如下的两难选择:重启如果优先加载RDB,加载速度更快,但是数据不是很全;如果优先加载AOF,加载速度会变慢,但是数据会比RDB中的要完整。
要解决这个两难问题的方案就是使用AOF和RDB混合持久化。

学习链接

redis之详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值