• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

Redis持久化详解(RDB与AOF)

Redis 彭东稳 8年前 (2016-07-04) 27233次浏览 已收录 3个评论

一、Redis持久化方案

Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘。当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

Redis提供了多种不同级别的持久化方式:一种是RDB,另一种是AOF。

RDB持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot),将数据库的快照(snapshot)以二进制的方式保存到磁盘中。

AOF持久化记录服务器执行的所有更改操作命令,AOF文件中的命令全部以Redis协议的格式来保存,新命令会被追加到文件的末尾。Redis还可以在后台对AOF文件进行重写(rewrite),使得AOF文件的体积不会超出保存数据集状态所需的实际大小。

Redis可以同时使用AOF持久化和RDB持久化。在这种情况下,当Redis重启时,它会优先使用AOF文件来还原数据集,因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。你甚至可以关闭持久化功能,让数据只在服务器运行时存在。

了解RDB持久化和AOF持久化之间的异同是非常重要的,以下几个小节将详细地介绍这这两种持久化功能,并对它们的相同和不同之处进行说明。

二、RDB快照

下面我们说一下Redis的第一个持久化策略,RDB快照。Redis支持将当前内存数据的快照存成一个数据文件的持久化机制,而一个持续写入的数据库如何生成快照呢?Redis巧妙地借助了fork命令的写时复制(copy on write)机制,将当前进程fork出一个子进程,子进程根据内存快照,循环将数据持久化为RDB文件。

在默认情况下, Redis将数据库快照保存在根目录下,名字为dump.rdb的二进制文件中。可通过参数dir配置指定保存目录,dbfilename指定文件名。你可以对Redis进行设置,如“save N M”,表示N秒内数据项中有M个改动时这一条件被满足时,自动保存一次数据集。比如你可以配置当10分钟以内有100次写入就生成快照,也可以配置当1分钟内有1000次写入就生成快照,支持可以多个规则一起生效,当匹配到哪个就哪个规则生效。这些规则的定义就在Redis的配置文件中,你也可以通过Redis的CONFIG SET命令在Redis运行时设置规则,不需要重启Redis。

比如说,以下设置会让Redis在满足“60秒内有至少有1000个键被改动”这一条件时,自动保存一次数据集:

你也可以通过调用SAVE或者BGSAVE,手动让Redis进行数据集保存操作。SAVE命令执行一个前台同步操作,以RDB文件的方式保存所有数据的快照,很少在生产环境直接使用SAVE命令,因为它会阻塞所有的客户端的请求,不要在生产环境使用,可以使用BGSAVE命令代替。BGSAVE命令执行过程中是通过fork一个子进程来完成的,所以主进程不会阻塞客户端请求,只有主进程fork子进程时会短暂阻塞读写操作(具体阻塞多久需要看内存大小,以及fork操作的开销,下面会介绍)。另外,在自动触发RDB持久化时,Redis也会选择BGSAVE而不是SAVE来进行持久化。

Redis自动RDB持久化在其内部是通过serverCron周期性操作函数、dirty计数器、和lastsave时间戳来实现的。其中serverCron每100ms执行一次,检查服务器状态,其中就包括检查“save N M”是否满足条件,如果满足就执行BGSAVE;当然也包括AOF重写检查。dirty计数器是Redis服务器维持的一个状态,记录了上一次执行BGSAVE/SAVE命令后,服务器状态进行了多少次修改(包括增删改),而当BGSAVE/SAVE执行完成后,会将dirty重新置为0。lastsave时间戳也是Redis服务器维持的一个状态,记录的是上一次成功执行BGSAVE/SAVE的时间,当前时间减去lastsave需满足M。

除了手动和自动以外,还有一些其他情况会触发BGSAVE:

  • 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行BGSAVE命令,并将rdb文件发送给从节点。
  • 执行shutdown命令时,自动执行rdb持久化。

另外需要了解的,因为其写操作是在一个新进程中进行的,当生成一个新的RDB文件时,Redis生成的子进程会先将数据写到一个临时文件中,然后通过原子性rename系统调用将临时文件重命名为RDB文件,这样在任何时候出现故障,Redis的RDB文件都总是可用的。

这种持久化方式被称为快照(snapshot)。但是,我们可以很明显的看到,RDB有他的不足,就是一旦数据库出现问题,那么我们的RDB文件中保存的数据并不是全新的,从上次RDB文件生成到Redis停机这段时间的数据全部丢掉了。在某些业务下,如果可以忍受间隔内数据丢失,我们也推荐这些业务使用RDB的方式进行持久化,因为开启RDB的代价并不高。但是对于另外一些对数据安全性要求极高的应用,无法容忍数据丢失的应用,RDB就无能为力了,所以Redis引入了另一个重要的持久化机制,AOF日志方式持久化。

为了尽可能使RDB文件体积减小,Redis默认采用LZF算法对RDB文件进行压缩。虽然压缩耗时,但是可以大大减小RDB文件的体积,因此压缩默认开启,参数为rdbcompression。需要注意的是,RDB文件的压缩并不是针对整个文件进行的,而是对数据库中的字符串进行的,且只有在字符串达到一定长度(20字节)时才会进行。

除了压缩,你也可以检验RDB文件,通过参数rdbchecksum设置,默认为yes。在写入文件和读取文件时都起作用,关闭checksum在写入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现。

另外,当bgsave出现错误时,Redis是否停止执行写命令。Redis提供了一个参数stop-writes-on-bgsave-error,设置为yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失;设置为no,则Redis无视bgsave的错误继续执行写命令,当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no。

说说fork的开销?

父进程通过fork操作可以创建子进程,第一代Unix系统实现了一种傻瓜式的进程创建:当执行fork系统调用时,内核复制父进程的整个用户空间并把复制得到的那一份分配给子进程。这种行为时非常耗时的,因为它需要完成以下几项任务:为子进程的页表分配页面、为子进程的页分配页面、初始化子进程的页表、把父进程的页复制到子进程对应的页中。

现代Linux的fork调用早已支持了写时拷贝(copy-on-write)技术,写时拷贝是一种可以推迟甚至免除拷贝父进程数据的技术。此时子进程只需要拷贝父进程的page table,然后把内存设置为只读,让父进程和子进程共享数据空间,当父进程或子进程需要修改共享的内存时,才需要复制出私有的page,在新的page上做修改。这也是为什么Redis建议把Linux的Transparent Huge Pages(THP)关闭。在THP下,一个物理page会保存更多的key,因此父进程在处理写操作时可能会复制更多的内存。

把写时复制技术带入到Redis中后,整个过程如下:Redis调用fork,现在有了子进程和父进程。父进程继续处理用户请求,子进程负责将内存内容写入到临时文件。由于系统的写时复制机制(copy on write),父子进程会共享相同的物理页面,当父进程处理写请求时,系统会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照。当子进程将快照写入临时文件完毕后,用临时文件替换原来的RDB快照文件,然后子进程退出。

由此可以看出,所以就算fork很大内存的进程,对额外内存的消耗也不会很大。可以通过Redis的日志查看做RDB的过程中,有多少copy-on-write内存(Dirty Pages):

这个值也表示Redis在做RDB的过程中所用到的额外的内存,如果内存很大,并且写操作频繁,那么额外的内存占用可能也不小,系统需要有足够的额外内存防止OOM。

现在虽然fork时,子进程不会复制父进程的数据空间,但是会复制内存page table(页表存储虚拟内存到物理内存的指针映射),这个复制是主线程来做的,会阻塞所有的读写操作,并且随着内存使用量越大耗时越长,可以在info stats统计中查latest_fork_usec指标获取最近一次fork操作耗时,单位(微秒)。

例如,在 Linux 系统上,内存默认分为 4kB 一个页(默认未开启 Transparent Huge Pages),为了将虚拟地址转换为物理地址,每个进程都存储一个页表(实际上表示为一颗树),该页表至少包含该进程地址空间的每页指针。因此,一个 24 GB 的 Redis 实例需要 24GB / 4kB * 8 = 48MB 的页表。也就是说,一次 fork,主进程至少要复制 48MB 的内存,这需要时间和 CPU,特别是在虚拟机上,其中一个大的内存块的分配和初始化是非常昂贵的。关于 fork 的时间消耗,可以看官方给出的数据,Fork time in different systems

如果想查看系统的页表内存开销,使用下面指令:

如果你想看父子进程自己的页表开销分别是多大,可以通过下面的方式得到:

关于 fork 使用的一些改善措施:

改善措施 1)优先使用物理机或者高效支持fork操作的虚拟化技术。

2)控制redis单实例的内存大小。fork耗时跟内存量成正比,线上建议每个Redis实例内存控制在10GB以内。

3)适度放宽AOF rewrite触发时机,目前线上配置:auto-aof-rewrite-percentage增长100%

子进程开销监控与优化 cpu

1)不要和其他CPU密集型服务部署在一起,造成CPU过度竞争。

2)如果部署多个Redis实例,尽量保证同一时刻只有一个子进程执行重写工作。

3)1G内存fork时间约20ms。

内存子进程通过fork操作产生,占用内存大小等同于父进程,理论上需要两倍的内存来完成持久化操作,但Linux有写时复制机制(copy-on-write)。父子进程会共享相同的物理内存页,当父进程处理写请求时会把要修改的页创建副本,而子进程在fork操作过程中共享整个父进程内存快照。Fork耗费的内存相关日志:AOF rewrite: 53 MB of memory used by copy-on-write,RDB: 5 MB of memory used by copy-on-write关闭巨页,开启之后,复制页单位从原来4KB变为2MB,增加fork的负担,会拖慢写操作的执行时间,导致大量写操作慢查询。

“sudo echo never > /sys/kernel/mm/transparent_hugepage/enabled

硬盘

不要和其他高硬盘负载的服务部署在一起。如:存储服务、消息队列。

这个问题也是导致 Redis 内存不宜过大的原因之一,当然还有导致故障恢复时间延长也是 Redis 内存不宜过大的原因。

三、AOF日志

通过上面的分析,我们知道RDB快照有大概率丢失最近写入、且仍未保存到快照中的那些数据。尽管对于某些程序来说,数据安全并不是最重要的考虑因素,但是对于那些追求数据安全的程序来说,快照功能就不太适用了。从1.1版本开始,Redis增加了一种实时性更好的持久化方式,即AOF持久化。AOF日志的全称是append only file,从名字上我们就能看出来,它是一个追加写入的日志文件。与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案。

AOF文件与MySQL数据库的binlog不同的是,AOF是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点,它记录的内容就是一个个的Redis标准命令。开启AOF持久化命令如下:

从现在开始,每当Redis执行一个改变数据集的命令时,比如SET,这个命令就会被追加到AOF文件的末尾。这样的话,当Redis重新启时,程序就可以通过重新执行AOF文件中的命令来达到重建数据集的目的。虽然写入到AOF文件,但还需要同步到磁盘,常用的同步硬盘的策略是everysec,用于平衡性能和数据安全性。对于这种方式,Redis使用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时,会造成Redis主线程阻塞。

由于Redis主线程需要记录Redis的每条写命令,因此AOF不需要触发,下面介绍AOF的执行流程:

Redis持久化详解(RDB与AOF)

第一步:首次命令写入(append)

首次命令写入操作,Redis主线程先将写命令追加到aof_buf(源码:flushAppendOnlyFile),然后调用系统write操作,write完成后主线程返回,而不会直接fsync到物理硬盘,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。

第二步:文件写入(write)和文件同步(sync)

Redis提供了多种AOF缓存区的同步文件策略,在现代操作系统中,为了提高文件写入效率,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果服务器宕机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

AOF线程负责同步AOF缓存区到磁盘文件,并且会记录最近一次同步时间。对于同步文件策略由appendfsync参数控制,各个值的含义如下:

  • always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
  • no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
  • everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是推荐的配置。

第三步:后续命令写入(append)

除了首次命令写入操作之外,后面所有写入操作,Redis主线程会负责对比上次AOF同步时间:如果距上次同步成功时间在2秒内,主线程直接返回。如果距上次同步成功时间超过2秒,主线程将调用AOF磁盘同步线程进行阻塞,直到磁盘同步操作完成,此时Redis不可用。

Tips:当我们打开AOF持久化功能后,Redis处理完每个事件后会调用write()将变化写入kernel的buffer,如果此时write()被阻塞,Redis就不能处理下一个事件。Linux规定执行write()时,如果对同一个文件正在执行fdatasync()将kernel buffer写入物理磁盘操作,write()会被Block住,那么整个Redis就会被Block住。

Redis提供了一个自救的方式,当发现文件有在执行fdatasync()时,就先不调用write(),只存在cache里,免得被Block。但如果已经超过两秒都还是这个样子,则会硬着头皮执行write(),即使redis会被Block住。

如果AOF缓冲区的文件同步策略为everysec,那么在主线程中,命令写入aof_buf后调用系统write操作,write完成后主线程返回;fsync同步文件操作由专门的文件同步线程每秒调用一次。这种做法的问题在于,如果硬盘负载过高,那么fsync操作可能会超过1s;如果Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗更快;如果此时Redis进程异常退出,everysec配置最多可能丢失2秒数据,不是1秒。还有一个更可怕的问题就是,主线程写aof_buf对比上一次fsync时间大于2s了,那么此时会强行等待write成功了,等多久,Redis就会Block多久。具体看Redis AOF刷新策略分析

AOF追加阻塞问题定位的方法,监控info Persistence中的aof_delayed_fsync,当AOF追加阻塞发生时(即主线程等待fsync而阻塞),该指标累加。另外,AOF阻塞时的Redis日志:Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis。如果AOF追加阻塞频繁发生,说明系统的硬盘负载太大;可以考虑更换IO速度更快的硬盘,或者通过IO监控分析工具对系统的IO负载进行分析。

文件重写(rewrite)

因为AOF的运作方式是不断地将命令追加到文件的末尾,所以随着写入命令的不断增加,AOF文件的体积也会变得越来越大。举个例子,如果你对一个计数器调用了100次INCR,那么仅仅是为了保存这个计数器的当前值,AOF文件就需要使用100条记录(entry)。然而在实际上,只使用一条SET命令已经足以保存计数器的当前值了,其余99条记录实际上都是多余的。另外还有一些过期的数据,无效的数据也都是可以去除。

过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。为了处理这种情况,Redis支持一种有趣的特性,可以在不打断服务客户端的情况下,对AOF文件进行重建(rebuild)。执行BGREWRITEAOF命令, Redis将生成一个新的AOF文件, 这个文件包含重建当前数据集所需的最少命令。

AOF REWRITE(重写)生成过程和RDB快照类似,都巧妙地利用了写时复制机制。同样也是fork一个子进程(此时主线程是阻塞的),子进程根据内存快照,按照命令合并规则写入到新的AOF文件。当主进程fork完子线程后继续接受请求,所有写命令依然写入AOF缓冲区(aof_buf),并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。但由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(aof_rewrite_buf)保存这部分新日志,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。

当子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。然后父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。然后调用原子性的rename命令用新的AOF文件取代老的AOF文件,完成AOF重写。

这里需要注意,因为由主进程把aof_rewrite_buf缓存追加到新日志文件。主进程追加日志时,不会处理其他请求,如果aof_rewrite_buf特别大,例如几百M,也可能造成Redis几秒甚至几十秒不响应。

NOTE

从上面的流程我们能够看到,RDB和AOF操作都是顺序IO操作,性能都很高。而在通过RDB文件或者AOF日志进行数据库恢复的时候,也是顺序的读取数据加载到内存中。所以也不会造成磁盘的随机读。

文件重写的触发,分为手动触发和自动触发:

  • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。
  • 自动触发:根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。

auto-aof-rewrite-min-size表示执行AOF重写时,文件的最小体积,默认值为64MB。auto-aof-rewrite-percentage表示执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值,即增长比例达到设定值。只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个参数同时满足时,才会自动触发AOF重写,即bgrewriteaof操作。

其中,参数可以通过config get命令查看:

状态可以通过info persistence查看:

另外在aof rewrite过程中,是否采取增量”文件同步”策略,由参数aof-rewrite-incremental-fsync控制,默认为”yes”,而且必须为yes。rewrite过程中,每32M数据进行一次fsync文件同步操作,这样可以减少”aof大文件”写入对磁盘的操作次数。

bgrewriteaof机制,在一个子进程中进行aof的重写,从而不阻塞主进程对其余命令的处理,同时解决了aof文件过大问题。现在问题出现了,同时在执行bgrewriteaof操作和主进程写aof文件的操作,两者都会操作磁盘,而bgrewriteaof往往会涉及大量磁盘操作,这样就会造成主进程在写aof文件的时候出现阻塞的情形,现在no-appendfsync-on-rewrite参数出场了。

如果该参数设置为no,是最安全的方式,不会丢失数据,但是要忍受阻塞的问题。如果设置为yes呢?这就相当于将appendfsync设置为no,这说明并没有执行磁盘操作,只是写入了缓冲区,因此这样并不会造成阻塞(因为没有竞争磁盘),但是如果这个时候Redis挂掉,就会丢失数据。丢失多少数据呢?在Linux的操作系统的默认设置下,最多会丢失30s的数据。因此,如果应用系统无法忍受延迟,而可以容忍少量的数据丢失,则设置为yes。如果应用系统无法忍受数据丢失,则设置为no。

对于pipelining有什么不同?

对于pipelining的操作,其具体过程是客户端一次性发送N个命令,然后等待这N个命令的返回结果被一起返回。通过采用pipilining就意味着放弃了对每一个命令的返回值确认。由于在这种情况下,N个命令是在同一个执行过程中执行的。所以当设置appendfsync为everysec时,可能会有一些偏差,因为这N个命令可能执行时间超过1秒甚至2秒。但是可以保证的是,最长时间不会超过这N个命令的执行时间和。

如果AOF文件出错了,怎么办?

服务器可能在程序正在对AOF文件进行写入时停机,如果停机造成了 AOF 文件出错(corrupt),那么Redis在重启时会拒绝载入这个AOF文件, 从而确保数据的一致性不会被破坏。当发生这种情况时,可以用以下方法来修复出错的 AOF 文件:为现有的AOF文件创建一个备份。然后使用Redis附带的redis-check-aof --fix程序对原来的AOF文件进行修复。

然后可选使用 diff -u 对比修复后的 AOF 文件和原始 AOF 文件的备份,查看两个文件之间的不同之处。再次重启Redis服务器,等待服务器载入修复后的AOF文件,并进行数据恢复。

但如果是AOF文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且aof-load-truncated参数开启,则日志中会输出警告,Redis忽略掉AOF文件的尾部,启动成功。aof-load-truncated参数默认是开启的。

四、RDB和AOF优缺点

RDB的优点?

RDB是一个非常紧凑(compact)的文件,体积小,网络传输快,它保存了Redis在某个时间点上的数据集。这种文件非常适合用于进行备份,恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。父进程在保存RDB文件时唯一要做的就是fork出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。

RDB的缺点?

RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

AOF的优点?

与RDB持久化相对应,AOF的优点在于支持秒级持久化、兼容性好。你可以设置不同的fsync策略,比如无fsync,每秒钟一次fsync,或者每次执行写入命令时fsync。AOF的默认策略为每秒钟fsync一次,在这种配置下,Redis仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据。AOF文件是一个只进行追加操作的日志文件(append only log),因此对AOF文件的写入不需要进行seek(查找),即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等), redis-check-aof 工具也可以轻易地修复这种问题。

Redis可以在AOF文件体积变得过大时,自动地在后台对AOF进行重写: 重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的AOF文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失。 而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析(parse)也很轻松。导出(export)AOF文件也非常简单: 举个例子, 如果你不小心执行了FLUSHALL命令,但只要AOF文件未被重写,那么只要停止服务器,移除AOF文件末尾的FLUSHALL命令,并重启 Redis, 就可以将数据集恢复到FLUSHALL执行之前的状态。

AOF的缺点?

AOF文件的体积通常要大于RDB文件的体积、且恢复速度慢。对于相同的数据集来说,根据所使用的fsync策略,AOF的速度可能会慢于RDB。在一般情况下,每秒fsync的性能依然非常高,而关闭fsync可以让AOF的速度和RDB一样快。另外,AOF在过去曾经发生过这样的bug,因为个别命令的原因,导致AOF文件在重新载入时,无法将数据集恢复成保存时的原样。虽然这种bug在AOF文件中并不常见,但是对比来说,RDB几乎是不可能出现这种bug的。

RDB和AOF,我应该用哪一个?

首先要明白无论是RDB还是AOF,持久化的开启都是要付出性能方面代价的:对于RDB持久化,一方面是bgsave在进行fork操作时Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO压力。但如果业务能容忍几分钟到10几分钟的数据丢失(且不使用备库),RDB是一个不错的选择;不然,就选择AOF。

对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO压力更大,甚至可能造成AOF追加阻塞问题(后面会详细介绍这种阻塞),此外,AOF文件的重写与RDB的bgsave类似,会有fork时的阻塞和子进程的IO压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对Redis主进程性能的影响会更大。

在实际生产环境中,根据数据量、应用对数据的安全要求、预算限制等不同情况,会有各种各样的持久化策略;如完全不使用任何持久化、使用RDB或AOF的一种,或同时开启RDB和AOF持久化等。此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机master和从机slave可以独立的选择持久化方案。比如完全关闭master持久化(包括RDB和AOF),这样可以让master的性能达到最好;而slave可以只开启AOF。但这种情况下,如果master服务因为故障宕掉了,如果系统中有自动拉起机制(即检测到服务停止后重启该服务)将master自动重启,由于没有持久化文件,那么master重启后数据是空的,slave同步数据也变成了空的,意味着数据丢失。所以尽量避免这种情况出现。

RDB和AOF之间的相互作用?

在版本号大于等于2.4的Redis中,BGSAVE执行的过程中,不可以执行BGREWRITEAOF。反过来说,在BGREWRITEAOF执行的过程中,也不可以执行BGSAVE。这可以防止两个Redis后台进程同时对磁盘进行大量的I/O操作。

如果BGSAVE正在执行,并且用户显示地调用BGREWRITEAOF命令,那么服务器将向用户回复一个OK状态,并告知用户,BGREWRITEAOF已经被预定执行: 一旦BGSAVE执行完毕,BGREWRITEAOF就会正式开始。当Redis启动时,如果RDB持久化和AOF持久化都被打开了,那么程序会优先使用AOF文件来恢复数据集,因为AOF文件所保存的数据通常是最完整的。

五、RDB和AOF数据导入

这些持久化的数据有什么用,当然是用于重启后的数据恢复。Redis是一个内存数据库,无论是RDB还是AOF,都只是其保证数据恢复的措施。所以Redis在利用RDB或AOF进行恢复的时候,会读取RDB或AOF文件,重新加载到内存中。相对于MySQL等数据库的启动时间来说,会长很多,因为MySQL本来是不需要将数据加载到内存中的。

但是相对来说,MySQL启动后提供服务时,其被访问的热数据也会慢慢加载到内存中,通常我们称之为预热,而在预热完成前,其性能都不会太高。而Redis的好处是一次性将数据加载到内存中,一次性预热。这样只要Redis启动完成,那么其提供服务的速度都是非常快的。

而在利用RDB和利用AOF启动上,其启动时间有一些差别。RDB的启动时间会更短,原因有两个,一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作。在CPU消耗上要远小于AOF日志的加载。

注意:当redis启动时,如果rdb持久化和aof持久化都打开了,那么程序会优先使用aof方式来恢复数据集,因为aof方式所保存的数据通常是最完整的。如果aof文件丢失了,则启动之后数据库内容为空。

注意:如果想把正在运行的redis数据库,从RDB切换到AOF,建议先使用动态切换方式,再修改配置文件,重启数据库。(不能直接修改配置文件,重启数据库,否则数据库中数据就为空了。)

在Redis 2.2或以上版本,可以在不重启的情况下,从RDB切换到AOF :

为最新的dump.rdb文件创建一个备份,将备份放到一个安全的地方。执行以下两条命令:

确保命令执行之后,数据库的键的数量没有改变。确保写命令会被正确地追加到 AOF 文件的末尾。

步骤2是开启了AOF功能,Redis会阻塞直到初始AOF文件创建完成为止,之后Redis会继续处理命令请求,并开始将写入命令追加到AOF文件末尾。

步骤3用于关闭RDB功能,这一步是可选的,如果你愿意的话,也可以同时使用RDB和AOF这两种持久化功能。

<延伸>

Fork三部曲之clone的诞生

https://redis.io/topics/latency


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (4)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!

(3)个小伙伴在吐槽