Redis第5讲——RDB、AOF和混合持久化机制
全文8412字。
球友们,大家早上好,这两天总结了一下Redis持久化相关的内容,在面试中的重要性就不用我多说了,相信很多小伙伴都被问到过😂总结下来才发现我之前了解的片面了(也可能我是个菜鸡的原因😅),目录如下:
一、RDB:简介、手动执行SAVE和BGSAVE命令、自动执行(redis.conf配置)、RDB文件载入和实现原理
二、AOF:简介、AOF持久化的实现(命令追加、文件写入和文件同步)、AOF文件载入、AOF重写的原因和实现流程、重写机制的触发(手动和自动)
三、RDB vs AOF:数据可靠性、性能、存储空间、使用场景等方面的优缺点,Redis4.0之前RDB和AOF同时开启会怎样
四、RDB和AOF混合持久化:Redis 4.0之后的功能(实现及优缺点)
原文博客:Redis第5讲——RDB、AOF和混合持久化机制
正文开始。。。。。
我们知道Redis是内存数据库,它把数据都存储在了内存中,如果Redis服务器出现了意外,比如宕机、断电等情况,那么内存中的数据就会全部丢失。所以必须有一种机制可以把内存中的数据保存到磁盘里面,为了解决这个问题,Redis提供了RDB和AOF两种持久化机制,这也是Redis的重要特性之一。
一、RDB(默认开启)
1.1 简介
RDB全称Redis Database Backup file,也被称为Redis数据快照。
RDB持久化功能生成的RDB文件是一个经过压缩的二进制文件(默认为dump.rdb,也可以在redis.conf文件中修改),默认保存在当前的运行目录(也可以在redis.conf文件中修改),简单来说就是把内存中的数据都记录在磁盘上,当Redis出现意外,可以通过Redis重启加载该文件来恢复数据。
RDB持久化既可以手动执行,也可以根据服务器配置选项定期自动执行。
1.2 手动执行(SAVE、BGSAVE)
可以用SAVE和BGSAVE命令来手动生成RDB文件,但这两者有所不同。
1.2.1 SAVE命令
- SAVE命令会阻塞Redis进程,直到RDB文件创建完毕为止,在这之前,服务器不能处理任何命令请求。
SAVE <指定时间间隔> <执行指定次数更新操作>
ps:Redis的数据操作都是单线程。
1.2.2 BGSAVE命令
- BGSAVE命令会创建一个子进程来创建RDB文件,所以在创建过程中,Redis服务器仍然可以处理客户端的命令请求,但是会拒绝SAVE、BGSAVE和BGREWRITEAOF三个命令的执行。
- SAVE:为了避免父进程(服务器进程)和子进程同时执行两个rdbSave调用,防止产生竞态条件。
- BGSAVE:也是防止产生竞态条件。
- BGREWRITEAOF:此命令用于异步执行一个AOF重写操作,如果正在执行BGSAVE命令,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完再执行。如果反过来,则会拒绝BGSAVE命令。因为它俩都是子进程在执行,而且这俩子进程都同时执行大量的磁盘写入操作,会影响服务性能。
1.2.3 RDB文件载入(恢复数据)
服务器在恢复数据库数据(载入RDB文件)时,会一直处于阻塞状态,直到载入完成为止。
1.3 自动执行
上面提到了两个命令SAVE和BGSAVE,SAVE是阻塞式的,因此其可用性欠佳,如果在数据量较少的情况下,基本上体会不到两个命令的差别,不过还是建议使用BGSAVE。
因为BGSAVE是非阻塞的,所以Redis允许通过配置redis.conf文件中的save配置,让服务器每隔一段时间自动执行一次BGSAVE命令,可以设置多个保存条件,比如以下3个默认条件(注释已翻译为中文):
1.4 实现原理
当Redis服务启动时,用户可以通过指定配置文件或者传入启动参数的方式设置save选项,如果没有主动设置,服务器就会使用redis.conf文件中默认的条件(上述3个条件)。
接着,服务器会根据save的选项所设置的保存条件,设置服务器状态redisServer结构的saveparams属性,除此之外,还有一个dirty计数器及lastsave属性:
- saveparams属性:是一个数组,每个saveparams结构都保存了一个save选项设置的保存条件:
- dirty属性:记录上一次成功成功执行SAVE命令或BGSAVE命令之后,服务器对数据库(全部数据库)进行了多少次修改(包括写入、删除、更新等操作)。
- lastsave属性:是一个UNIX时间戳,记录服务器上一次成功执行SAVE或BGSAVE命令的时间。
那么它是如何被调用的呢?
Redis的服务器有一个周期性操作函数serverCron,它每隔100ms就会执行一次,该函数用于对正在运行的服务器进行维护,其中的一项工作就是检查save选项所设置的保存条件是否已满足,如果满足就执行BGSAVE命令。
二、AOF(默认关闭)
2.1 简介
AOF全称Append Only File,是Redis另外一种持久化机制,默认不开启(可在redis.conf文件中配置),它的目的是为了解决生成RDB文件后数据不能实时一致的问题,所以它采用日志的形式来记录每个写操作,并追加到文件中。Redis重启会根据日志文件的内容将写命令从前到后执行一遍来恢复数据。
ps:redis 7.0有个新特性Multi-part AOF,即把单个的AOF文件拆成了多个AOF文件,在MP-AOF中,有三种类型的AOF:
- BASE:基础文件(只有一个)
- appendonly.aof.1.base.rdb
- INCR:增量文件(有多个)
- appendonly.aof.1.incr.aof
- appendonly.aof.2.incr.aof
- mainfest:清单文件
- appendonly.aof.mainfest
2.2 AOF持久化的实现
AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤:
2.2.1 命令追加
- 服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。aof缓冲区是redisServer结构体维护的一个SDS结构的属性。
2.2.2 文件写入
- 将aof缓冲区的数据写入到AOF文件,此时数据并没有写入到硬盘,而是拷贝到了内核缓冲区page cache(操作系统),等待内核将数据写入硬盘;具体内核缓冲区的数据什么时候写入到硬盘,由内核决定。
2.2.3 文件同步
这个过程是将内核缓冲区中的数据写入到硬盘中的AOF文件中。如果由内核决定将内核数据写入硬盘的话,如果服务器宕机,那么就会丢失数据。为了解决这个问题,系统提供了fync和fdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘,以及三种策略:
- always:同步写回,每个写命令执行完立刻同步地将日志写回磁盘。(性能最差,最多丢失一个写指令的数据)
- everysec(默认):每秒执行一次。(是性能和数据安全性的折中方案,最多也就丢一秒的数据)
- no:根据操作系统和资源的情况,一定时间执行一次,时间不确定。(性能最好,可能会丢失上次同步AOF文件之后的所有写命令数据)
下面是redis.conf文件中的配置:
2.3 AOF文件载入(恢复数据)
因为AOF文件里包含了所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面的命令,就可以还原服务器关闭之前的数据,详细步骤如下:
- 创建一个不带网络连接的伪客户端(fake client),因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,效果和带网络连接的客户端一样。
- 从AOF文件中分析并读取一条写命令。
- 使用伪客户端执行被读出的写命令。
- 重复执行2和3步骤,直到AOF文件中的所有写命令都被处理完毕为止。
完成以上步骤后,AOF文件所保存的数据就会被完整地还原出来。
2.4 AOF重写
2.4.1 原因
因为AOF是通过保存被执行的写命令来持久化的,所以随着Redis的长时间运行,AOF的体积会越来越大,那么这就会带来以下几个问题:
- Linux文件系统对单文件的大小有限制,AOF文件过大的话无法保存。
- 文件过大,持久化追加写命令的时候效率也会很低。
- 数据载入(恢复)就更不用说了,它本身就是一条一条执行的,那就会导致执行的时间更久。
简而言之,AOF文件过大很可能对Redis服务器、甚至整个宿主机造成影响,所以Redis提供了重写的策略来应对这个问题。
2.4.2 举个例子
先举个例子:
我们可以看到,光是记录这个list键的数据就要在AOF文件中追加六条命令。如果在实际项目中,AOF中单单一个key键的持久化可能就得被追加成百上千次,想想就很恐怖。
不知道你有没有发现,尽管执行了这么多命令,而最终想要的数据就是当前Redis数据库存的值,所以重写的时候只用再读一遍数据库,把对应的写命令重写到AOF文件中,那保存的内容岂不是大大减少了。就像上述例子,重写的时候只需要把"rpush list "C" "D" "E""命令追加到AOF文件就行了(仅需一条)。实际上Redis也是这么做的。
2.4.3 实现流程
Redis重写功能的大致流程如下
- Redis 会启动一个 AOF 重写子进程,负责执行 AOF 重写操作。同时,Redis 会继续处理新的写入命令,并将这个写命令发送给AOF缓冲区和AOF重写缓冲区。
- AOF缓冲区:无论重不重写都有这个缓冲区,AOF日志写入是AOF缓冲区->AOF文件。
- AOF重写缓冲区:AOF重写子进程启动后开始使用。
- 子进程会按照一定的规则,读取当前数据库的键值对,并将其转化为合适的命令格式,保存到AOF重写缓冲区中。
- 子进程在遍历完整个数据库之后,就完成了重写工作。
- 接着,服务器父进程就会执行以下操作:
- 将AOF重写缓冲区中的所有内容写入到新的AOF文件中,使得新的AOF文件于当前数据库中的数据一致。
- 对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替换。
- 最后,Redis 会关闭并销毁旧的 AOF 文件。
经过重写后的新的AOF文件只包含了恢复数据库数据所必须的命令,不会浪费任何空间。
ps:这么看来,AOF重写并未对旧的AOF文件进行任何读取、分析和写入操作,所以重写这个名称是有歧义的。
2.5 重写机制的触发
2.5.1 手动触发
手动执行BGREWRITEAOF命令,开始重写aof文件:
2.5.2 自动触发
可以在redis.conf文件中修改对应的配置,让服务器自动执行BGREWRITEAOF命令。
三、RDB VS AOF
RDB和AOF在数据可靠性、性能、存储空间、使用场景等方面都有不同的优缺点,具体可以根据实际业务需求和硬件条件选择合适的持久化机制,或者同时使用两种持久化机制来实现更高的数据可靠性:
- 数据可靠性
- RDB:可能会丢失最后一次快照之后的数据。如果RDB持久化过程中服务器宕机了,那么就会丢失这一次的数据。
- AOF:可能会丢失最后一次(或一秒)写操作的数据。如果Redis刚刚执行完一个写命令,还没来得及写AOF文件就宕机了,那么就会丢失这一条数据【当然也得看它配置的策略,如果配置的是always(同步),那就丢一条,配置的everysec(每秒)那就会丢1秒的数据】,但它也比RDB更加靠谱一些。
- 性能:
- RDB:备份和数据恢复比较快,适合做数据恢复。RDB存的是原生数据,所以直接加载到内存中即可。
- AOF:写性能较高【RDB是对整个物理中的数据的快照,AOF则仅仅是记录每次写命令】,但数据恢复速度相对较慢【AOF需要对命令从头到尾再执行一次】。
- 存储空间:
- RDB:二进制文件,体积较小。
- AOF:文本文件,体积较大。
- 使用场景:
- RDB:适用于需要定期备份、大规模数据恢复、恢复速度要求比较快的场景。
- AOF:适用于对数据完整性要求较高、数据存档的场景。
既然都有各自的优缺点,那么它俩同时开启会怎样?
Redis 4.0之前,如果两种方式同时开启,dump.rdb和appendonly.aof文件都会生成,但在恢复数据时,会优先用appendonly.aof来恢复(比较完整),但AOF恢复数据相对较慢,如果Redis实例比较大的情况下,启动要花费很长时间。但再Redis 4.0后就优化了这个问题,这也是下面即将介绍的内容。
四、RDB和AOF混合持久化
Redis 4.0为了解决上面的问题,带来了一个新的持久化选项——混合持久化。在开启混合持久化的情况下,AOF重写时会把Redis的持久化数据,以RDB的格式写入到AOF文件的开头,之后的数据再以AOF的格式追加到文件的末尾。
开启混合模式参数:
优缺点:
- 优点:混合持久化结合了RDB和AOF持久化的优点,开头为RDB格式,可以使Redis启动的更快,同时结合AOF的优点,又降低了大量数据丢失的风险。
- 缺点:在AOF文件中添加了RDB格式的内容,使得AOF文件的可读性变得很差;如果开始混合持久化,那么混合持久化的AOF是不能在旧版本中用的,不能向下兼容。
End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。
