您现在的位置是:首页 >技术教程 >Redis-RDB/AOF网站首页技术教程

Redis-RDB/AOF

静看º一季花开花落 2024-06-27 00:01:02
简介Redis-RDB/AOF

前言

​ 为了防止数据丢失以及服务重启时能够恢复数据,Redis支持数据的持久化,主要分为两种方式,分别是RDB和AOF; 当然实际场景下还会使用这两种的混合模式。

​ **Redis服务提供四种持久化存储方案:RDBAOF虚拟内存(VM)和 DISKSTORE。**虚拟内存(VM)方式,从Redis Version 2.4开始就被官方明确表示不再建议使用;至于DISKSTORE方式,是从Redis Version 2.8版本开始提出的一个存储设想,到目前为止Redis官方也没有在任何stable版本中明确建议使用这用方式。

RDB 持久化

RDB 就是 Redis DataBase 的缩写,中文名为快照/内存快照,RDB持久化是把当前进程数据生成快照保存到磁盘上的过程,由于是某一时刻的快照,那么快照中的值要早于或者等于内存中的值。触发rdb持久化的方式有2种,分别是手动触发和自动触发。

手动触发

save命令阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。

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

具体流程如下:

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

自动触发

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

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

redis.conf中配置RDB周期

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

save 60 10000
//如果60秒内有10000条Key信息发生变化,则进行快照。

​ **RDB中的核心思路是Copy-on-Write,来保证在进行快照操作的这段时间,需要压缩写入磁盘上的数据在内存中不会发生变化。**在正常的快照操作中,一方面Redis主进程会fork一个新的快照进程专门来做这个事情,这样保证了Redis服务不会停止对客户端包括写请求在内的任何响应。另一方面这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。

是否可以频繁执行全量快照

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

RDB优缺点

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

AOF 持久化

​ **Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。AOF日志采用写后日志,即先写内存,后写日志。**因为写内存相对而言很快,而已文件形式存在,就需要考虑磁盘IO问题,数据结构问题,但是Redis的日志是直接按照指令存储,故存储结构不需要进行考虑。

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

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

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

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

实现AOF

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hL6Ess02-1685084758810)(/Users/zhaoyanhong/Library/Application Support/typora-user-images/image-20230525182711204.png)]

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

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

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

​ **默认情况下,Redis是没有开启AOF的,可以通过配置redis.conf文件来开启AOF持久化。**AOF会记录每个写命令到AOF文件,随着时间越来越长,AOF文件会变得越来越大。如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,而且AOF文件越大,数据恢复也越慢。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。

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

​ fork采用操作系统提供的写时复制(copy on write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成阻塞。fork子进程时,子进程时会拷贝父进程的页表,即虚实映射关系(虚拟内存和物理内存的映射索引表),而不会拷贝物理内存。这个拷贝会消耗大量cpu资源,并且拷贝完成前会阻塞主线程,阻塞时间取决于内存中的数据量,数据量越大,则内存页表越大。拷贝完成后,父子进程使用相同的内存地址空间。

重写机制

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

​ **AOF重写过程是由后台进程bgrewriteaof来完成的。**主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。期间复制内存过程属于零拷贝,相对快,但是内存如果过大会存在一定的停顿,因为需要暂停一下主线程的服务,避免复制过程中有其他数据加入。

AOF重写重写时机

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

auto-aof-rewrite-min-size:表示运行AOF重写时文件的最小大小,默认为64MB。也就是说Redis服务启动之后,当文件大小超过64MB,就会触发AOF重写。

auto-aof-rewrite-percentage:表示当前AOF文件空间(aof_current_size) 和上一次重写后AOF文件空间(aof_base_size) 的比值。

​ 自动触发时机为:aof_current_size > auto-aof-rewrite-minsize && (aof_current_size-aof_base_size) / aof_base_size >= auto-aof-rewritepercentage

重写日志过程中,新数据插入?

​ **会先把新数据放入缓冲区,等待重写操作完成。在bgrewriteaof子进程完成会日志文件的重写操作后,会提示主线程已经完成重写操作,主线程会将AOF重写缓冲中的命令追加到新的日志文件后面。**这时候在高并发的情况下,AOF重写缓冲区积累可能会很大,这样就会造成阻塞,Redis后来通过Linux管道技术让aof重写期间就能同时进行回放,这样aof重写结束后只需回放少量剩余的数据即可

最后通过修改文件名的方式,保证文件切换的原子性。在AOF重写日志期间发生宕机的话,因为日志文件还没切换,所以恢复数据时,用的还是旧的日志文件。

总结操作

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

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

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

重启服务之后,文件加载流程?

  • 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阻塞。可以通过以下方式进行优化:

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

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

  • 如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回;
  • 自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据
  • 单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行
  • 可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令
  • RDB持久化与AOF持久化可以同时存在,配合使用
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。