Redis持久化之RDB与AOF详解

⭐️RDB和AOF机制

简介

RDB和AOF是redis数据持久化的两种机制;

当然实际场景下还会使用这两种的混合模式

为了防止数据丢失以及服务重启时能够恢复数据

❓为什么需要持久化

  • Redis是个基于内存的数据库。服务器一旦宕机,内存中的数据将全部丢失

  • 当然也可以从后端数据库恢复这些数据,但是后端数据库性能一般比不上redis,大数据量的恢复会对数据库带来巨大压力,所以应该避免从后端数据库中恢复数据

Redis持久化有哪些方式呢为什么我们需要重点学RDB和AOF

  • RDB
  • AOF
  • 虚拟内存VM(官方明确不建议使用)
  • DISKSTORE(官方没有在稳定版本中支持使用)

RDB

🆎 (Redis DataBase) ,中文名为快照/内存快照

RDB持久化是把当前进程数据生成快照保存到磁盘上的过程

触发机制

触发rdb持久化的方式有2种,分别是手动触发自动触发

1️⃣手动触发
  • save命令阻塞Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞线上环境不建议使用

  • bgsave命令Redis进程执行fork操作创建子进程去执行RDB持久化操作,完成后自动结束。阻塞只发生在fork阶段,一般时间很短

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-trYbmrlJ-1651558835805)(Redis.assets/image-20220107150109920.png)]

    具体流程如下:

    • redis客户端执行bgsave命令或者自动触发bgsave命令;
    • 主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回;
    • 如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作;
    • 子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件;
    • 同时发送信号给主进程,通知主进程rdb持久化完成,主进程更新相关的统计信息(info Persitence下的rdb_*相关选项)。
2️⃣自动触发

在以下4种情况时会自动触发

  • 根据配置文件:redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;
  • 主从复制时:从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;
  • 执行debug reload命令重新加载redis时也会触发bgsave操作;
  • 默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;

redis.conf配置RDB

快照周期:内存快照虽然可以通过技术人员手动执行SAVE或BGSAVE命令来进行,但生产环境下多数情况都会设置其周期性执行条件。

  • Redis中默认的周期新设置
# 周期性执行条件的设置格式为
save <seconds> <changes>

# 默认的设置为:
save 900 1      #15分钟内修改了1次,触发rdb
save 300 10		#6分钟内修改了10次,触发rdb
save 60 10000	#1分钟内修改了10000次,触发rdb

# 以下设置方式为关闭RDB快照功能
save ""

  • 其它相关配置
# 文件名称
dbfilename dump.rdb

# 文件保存路径
dir /home/work/app/redis/data/

# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes

# 是否压缩
rdbcompression yes

# 导入时是否检查
rdbchecksum yes
  

dbfilename:RDB文件在磁盘上的名称。

dir:RDB文件的存储路径。默认设置为“./”,也就是Redis服务的主目录。

stop-writes-on-bgsave-error:上文提到的在快照进行过程中,主进程照样可以接受客户端的任何写操作的特性,是指在快照操作正常的情况下。如果快照操作出现异常(例如操作系统用户权限不够、磁盘空间写满等等)时,Redis就会禁止写操作。这个特性的主要目的是使运维人员在第一时间就发现Redis的运行错误,并进行解决。一些特定的场景下,您可能需要对这个特性进行配置,这时就可以调整这个参数项。该参数项默认情况下值为yes,如果要关闭这个特性,指定即使出现快照错误Redis一样允许写操作,则可以将该值更改为no。

rdbcompression:该属性将在字符串类型的数据被快照到磁盘文件时,启用LZF压缩算法。Redis官方的建议是请保持该选项设置为yes,因为“it’s almost always a win”。

rdbchecksum:从RDB快照功能的version 5 版本开始,一个64位的CRC冗余校验编码会被放置在RDB文件的末尾,以便对整个RDB文件的完整性进行验证。这个功能大概会多损失10%左右的性能,但获得了更高的数据可靠性。所以如果您的Redis服务需要追求极致的性能,就可以将这个选项设置为no。

深入理解

由于生产环境中我们为Redis开辟的内存区域都比较大(例如6GB),那么将内存中的数据同步到硬盘的过程可能就会持续比较长的时间,而实际情况是这段时间Redis服务一般都会收到数据写操作请求。那么如何保证数据一致性呢

  • RDB中的核心思路是**Copy-on-Write**,来保证在进行快照操作的这段时间,需要写入磁盘上的数据在内存中不会发生变化。
  • 快照这段时间,父进程仍然响应包括写请求在内的任何请求。当父进程修改数据时,会将原先的数据复制一份生成新的副本进行修改,不会影响到子进程读取原来的数据,此时子进程的指针仍然指向旧的数据,子进程看到的数据还是bgsave时候的数据。当下一次执行bgsave时,新fork出来的子进程指针才会指向这次新的数据。

具体可以看这个

在进行快照操作的这段时间,如果发生服务崩溃怎么办

  • 在没有将数据全部写入到磁盘前,这次快照操作都不算成功
  • 如果出现了服务崩溃,将以上一次完整的RDB快照文件恢复内存数据
  • 快照过程是不影响上一次的备份的,因为会rdb时会先写入一个临时文件,然后操作成功后,才替换掉上一次的备份

可以每秒做一次快照吗

对于快照来说,所谓“连拍”就是指连续地做快照。这样一来,快照的间隔时间变得很短,即使某一时刻发生宕机了,因为上一时刻快照刚执行,丢失的数据也不会太多。但是,这其中的快照间隔时间就很关键了。

所以,要想尽可能恢复数据,t 值就要尽可能小,t 越小,就越像“连拍”。,比如说是不是可以每秒做一次快照?毕竟,每次快照都是由 bgsave 子进程在后台执行,也不会阻塞主线程。

**这种想法其实是错误的。**虽然 bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量快照,也会带来两方面的开销

  • 一方面,频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽,前一个快照还没有做完,后一个又开始做了,容易造成恶性循环。
  • 另一方面,fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。如果频繁 fork 出 bgsave 子进程,这就会频繁阻塞主线程

✌️ 解决方案

那么,有什么其他好方法吗?

  • 我们可以做**增量快照**,就是指做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。这个比较好理解。
  • 但是它需要我们使用额外的元数据信息去记录哪些数据被修改了,这会带来额外的空间开销问题

那么,还有什么方法既能利用 RDB 的快速恢复,又能以较小的开销做到尽量少丢数据呢?且看后文中4.0版本中引入的RDB和AOF的混合方式。

RDB优缺点

  • 优点
    • 占用内存小:RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
    • 速度快:Redis加载RDB文件恢复数据要远远快于AOF方式
  • 缺点
    • RDB方式实时性差,无法做到秒级的持久化;
    • 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
    • RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;
    • 版本兼容RDB文件问题;

针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决

AOF

引言

  • Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。
  • 日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。

✅PS: 大多数的数据库采用的是写前日志(WAL),例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。

简介

而AOF日志采用写后日志,即先写内存,后写日志在这里插入图片描述

为什么采用写后日志

Redis要求高性能,采用后写日志有两方面好处:

  • 避免额外的检查开销后写日志就不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
  • 不会阻塞当前的写操作

但这种方式存在潜在风险:

  • 如果命令执行完成,写日志之前宕机了,会丢失数据。
  • 主线程写磁盘压力大,导致写盘慢,阻塞后续操作。

如何实现AOF

AOF日志记录Redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync)。

  • 命令追加 当AOF持久化功能打开了,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 aof_buf 缓冲区。
  • 文件写入和同步 关于何时将 aof_buf 缓冲区的内容写入AOF文件中,Redis提供了三种写回策略:在这里插入图片描述

Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;

Everysec,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;

No,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

  • 三种写回策略的优缺点

上面的三种写回策略体现了一个重要原则:trade-off,取舍,指在性能和可靠性保证之间做取舍。

关于AOF的同步策略是涉及到操作系统的 write 函数和 fsync 函数的,在《Redis设计与实现》中是这样说明的:

  • 为了提高文件写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区的空间被填满或超过了指定时限后,才真正将缓冲区的数据写入到磁盘里。

  • 这样的操作虽然提高了效率,但也为数据写入带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失。为此,系统提供了fsync、fdatasync同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保写入数据的安全性。

redis.conf中配置AOF

默认情况下,Redis是没有开启AOF的,可以通过配置redis.conf文件来开启AOF持久化,关于AOF的配置如下:

# appendonly参数开启AOF持久化
appendonly no

# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"

# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./

# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no

# aof重写期间是否同步
no-appendfsync-on-rewrite no

# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加载aof出错如何处理
aof-load-truncated yes

# 文件重写策略
aof-rewrite-incremental-fsync yes

以下是Redis中关于AOF的主要配置信息:

appendonly:默认情况下AOF功能是关闭的,将该选项改为yes以便打开Redis的AOF功能。

appendfilename:这个参数项很好理解了,就是AOF文件的名字。

appendfsync:这个参数项是AOF功能最重要的设置项之一,主要用于设置“真正执行”操作命令向AOF文件中同步的策略。

什么叫“真正执行”呢?还记得Linux操作系统对磁盘设备的操作方式吗? 为了保证操作系统中I/O队列的操作效率,应用程序提交的I/O操作请求一般是被放置在linux Page Cache中的,然后再由Linux操作系统中的策略自行决定正在写到磁盘上的时机。而Redis中有一个fsync()函数,可以将Page Cache中待写的数据真正写入到物理设备上,而缺点是频繁调用这个fsync()函数干预操作系统的既定策略,可能导致I/O卡顿的现象频繁 。

与上节对应,appendfsync参数项可以设置三个值,分别是:always、everysec、no,默认的值为everysec。

no-appendfsync-on-rewrite:always和everysec的设置会使真正的I/O操作高频度的出现,甚至会出现长时间的卡顿情况,这个问题出现在操作系统层面上,所有靠工作在操作系统之上的Redis是没法解决的。为了尽量缓解这个情况,Redis提供了这个设置项,保证在完成fsync函数调用时,不会将这段时间内发生的命令操作放入操作系统的Page Cache(这段时间Redis还在接受客户端的各种写操作命令)。

auto-aof-rewrite-percentage:上文说到在生产环境下,技术人员不可能随时随地使用“BGREWRITEAOF”命令去重写AOF文件。所以更多时候我们需要依靠Redis中对AOF文件的自动重写策略。Redis中对触发自动重写AOF文件的操作提供了两个设置:auto-aof-rewrite-percentage表示如果当前AOF文件的大小超过了上次重写后AOF文件的百分之多少后,就再次开始重写AOF文件。例如该参数值的默认设置值为100,意思就是如果AOF文件的大小超过上次AOF文件重写后的1倍,就启动重写操作。

auto-aof-rewrite-min-size:参考auto-aof-rewrite-percentage选项的介绍,auto-aof-rewrite-min-size设置项表示启动AOF文件重写操作的AOF文件最小大小。如果AOF文件大小低于这个值,则不会触发重写操作。注意,auto-aof-rewrite-percentage和auto-aof-rewrite-min-size只是用来控制Redis中自动对AOF文件进行重写的情况,如果是技术人员手动调用“BGREWRITEAOF”命令,则不受这两个限制条件左右。

深入理解AOF重写

AOF会记录每个写命令到AOF文件,随着时间越来越长,AOF文件会变得越来越大。

如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,而且AOF文件越大,数据恢复也越慢。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”

图例解释AOF重写

Redis通过创建一个新的AOF文件来替换现有的AOF,新旧两个AOF文件保存的数据相同,但新AOF文件没有了冗余命令。

在这里插入图片描述

AOF重写会阻塞吗

AOF重写过程是由主线程fork出的bgrewriteaof子进程来完成的。主进程和子进程共享内存,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。

所以aof在重写时,在fork进程时是会阻塞住主线程的。

AOF日志何时会重写

有两个配置项控制AOF重写的触发:

auto-aof-rewrite-min-size:表示运行AOF重写时文件的最小大小,默认为64MB。

auto-aof-rewrite-percentage:这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值。

重写日志时,有新数据写入咋整

在这里插入图片描述

  • 在fork出子进程时在重写时,如果有新数据写入,主线程就会将命令记录到两个aof日志内存缓冲区(aof_bufaof_rewrite_buf)
  • 如果AOF写回策略配置的是always,则直接将命令写回旧的日志文件,并且保存一份命令至aof_rewirite_buf缓冲区,这些操作对新的日志文件是不存在影响的
  • 在bgrewriteaof子进程完成重写操作后,通知主线程已经完成重写操作,主线程会将aof_rewirite_buf中的命令追加到新的日志文件后面
  • 最后通过修改文件名的方式,替换日志文件。

总结操作

  • 主线程fork出子进程重写aof日志
  • 子进程重写日志完成后,主线程追加aof日志缓冲
  • 替换日志文件

❗️注意:

  • 在重写期间,由于主进程依然在响应命令,为了保证最终备份的完整性;因此它依然会写入旧的AOF file中,如果重写失败,能够保证数据不丢失。

  • 为了把重写期间响应的写入信息也写入到新的文件中,因此也会为子进程保留一个buf,防止新写的file丢失数据。

  • 重写是直接把当前内存的数据生成对应命令,并不需要读取老的AOF文件进行分析、命令合并。

  • 不管是RDB还是AOF都是先写入一个临时文件,然后通过 rename 完成文件的替换工作。

  • 在高并发的情况下,aof_rewirite_buf缓冲区积累可能会很大,这样就会造成阻塞,Redis后来通过Linux管道技术让aof重写期间就能同时进行回放,这样aof重写结束后只需回放少量剩余的数据即可。

  • 在AOF重写日志期间发生宕机的话,因为日志文件还没切换,所以恢复数据时,用的还是旧的日志文件。

主线程fork出子进程的是如何复制内存数据的

fork子进程时,子进程时会拷贝父进程对物理内存的引用,即指针。拷贝完成后,父子进程使用相同的内存地址空间。

主进程是可以有数据写入的,采用写时复制(copy on write)机制,拷贝数据内存的副本。如下图(进程1看做是主进程,进程2看做是子进程):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6TDBaSuN-1651559077565)(C:/Users/%E9%BB%84%E5%87%AF%E5%AE%87/Desktop/%E6%98%A5%E6%8B%9B/Redis/Redis.assets/redis-x-aof-3.png)]

在主进程有数据写入时,而这个数据刚好在页c中,操作系统会创建这个页面的副本(页c的副本),即拷贝当前页的物理数据,将其映射到主进程中,而子进程还是使用原来的的页c。

在重写日志整个过程时,主线程有哪些地方会被阻塞

  1. fork子进程时,需要拷贝虚拟页表(指针表,引用表),会对主线程阻塞。
  2. 主进程有bigkey写入时,操作系统会创建页面的副本,并拷贝原有的数据,会对主线程阻塞。
  3. 子进程重写日志完成后,主进程追加aof重写缓冲区时可能会对主线程阻塞。

为什么AOF重写不复用原AOF日志

  1. 父子进程写同一个文件会产生竞争问题,阻塞父进程
  2. 如果AOF重写过程中失败了,相当于污染了原本的AOF文件,无法做恢复数据使用。
  3. 本身就是原AOF过大了才进行重写的,复用读取消耗太大

RDB和AOF混合方式(4.0版本)

Redis 4.0 中提出了一个混合使用 AOF 日志和RDB快照的方法。

  • 简单来说RDB以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

🚨 好处:

  • 这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。
  • AOF 日志也只用记录两次快照间的操作,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。

如下图所示,T1 和 T2 时刻的修改,用 AOF 日志记录,等到第二次做全量快照时,就可以清空 AOF 日志,因为此时的修改都已经记录到快照中了,恢复时就不再用日志了。

在这里插入图片描述

这个方法既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势, 实际环境中用的很多。

从持久化中恢复数据

❓ 数据的备份、持久化做完了,我们如何从这些持久化文件中恢复数据呢?如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?

其实想要从这些文件中恢复数据,只需要重新启动Redis即可。我们还是通过图来了解这个流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-corWUXE3-1651558835811)(C:/Users/%E9%BB%84%E5%87%AF%E5%AE%87/Desktop/%E6%98%A5%E6%8B%9B/Redis/Redis.assets/redis-x-aof-5.png)]

  • redis重启时判断是否开启aof,如果开启了aof,那么就优先加载aof文件;
  • 如果aof存在,那么就去加载aof文件,加载成功的话redis重启成功,如果aof文件加载失败,那么会打印日志表示启动失败,此时可以去修复aof文件后重新启动;
  • 若aof文件不存在,那么redis就会转而去加载rdb文件,如果rdb文件不存在,redis直接启动成功;
  • 如果rdb文件存在就会去加载rdb文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么redis重启成功,且使用rdb文件恢复数据;

❓ 为什么会优先加载AOF呢?

因为AOF保存的数据更完整,通过上面的分析我们知道AOF基本上最多损失1s的数据

性能与实践

通过上面的分析,我们都知道RDB的快照、AOF的重写都需要fork,这是一个重量级操作,会对Redis造成阻塞。因此为了不影响Redis主进程响应,我们需要尽可能降低阻塞。

  • 降低fork的频率,比如可以手动来触发RDB生成快照、与AOF重写;
  • 控制Redis最大使用内存,防止fork耗时过长;
  • 使用更牛逼的硬件;
  • 合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败。

在线上我们到底该怎么做?我提供一些自己的实践经验。

  • 如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回;
  • 自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据;
  • 单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行;
  • 可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令;
  • RDB持久化与AOF持久化可以同时存在,配合使用。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值