一文搞懂Redis持久化方式RDB&AOF
Posted 沸羊羊_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文搞懂Redis持久化方式RDB&AOF相关的知识,希望对你有一定的参考价值。
前言
Redis
的性能好的特性很大程度上是由于将所有数据都存储在了内存中,然而当 Redis
重启后,所有存储在内存中的数据就会丢失,在一些情况下,我们希望 Redis
在重启后能保证数据不丢失。我们可以让数据从内存中以某种形式同步到磁盘中,使得重启后可以根据硬盘中的记录恢复数据。这一过程就是持久化。
Redis
支持两种方式的持久化,一种是 RDB
,一种是 AOF
,前者会根据指定的规则“定时”将内存中的数据存储在磁盘上,而后者在每次执行命令后将命令本身记录下来,两种持久化方式可以单独使用其中一种,但更多情况是两者结合使用。
RDB
RDB
方式的持久化是通过快照完成的,当符合一定条件时Redis
会自动将内存中的所有数据生成一份副本并存储在磁盘上,这个过程即为“快照”,Redis
会在以下几种情况下对数据进行快照:
- 根据配置规则进行自动快照
SAVE
或者BGSAVE
命令- 执行
flushall
命令 - 执行复制时
载入 RDB
文件的实际工作由 rdb.c/rdbLoad
函数完成,这个函数和 rdbSave
函数之间的关系如下图所示。
数据快照的四种情况
1、根据配置规则进行自动快照
Redis
允许用户自定义快照条件,当符合快照条件时,Redis
会自动执行快照操作,进行快照的条件可以由用户在配置文件中自定义,由两个参数构成:时间窗口M 和 改动的键的个数N,每当时间M内被更改的键的个数大于N时,即符合自动快照条件。例如:
- save 900 1
- save 300 10
- save 60 10000
可以同时存在多个条件,条件之间是“或”的关系,就这个例子,save 900 1
表示在 900s 内有一个或一个以上的键被更改则进行快照。
2、SAVE
/ BGSAVE
命令
除了让 Redis
自动除了让Redis自动进行快照外,当进行服务重启、手动迁移以及备份时我们也会需要手动执行快照操作。Redis提供了两个命令来完成这一任务。
SAVE
命令
当执行SAVE
命令时,Redis
同步地进行快照操作,在快照执行的过程中会阻塞所有来自客户端的请求。当数据库中的数据比较多时,这一过程会导致Redis
较长时间不响应,所以要尽量避免在生产环境中使用这一命令。
BGSAVE
命令
需要手动执行快照时推荐使用BGSAVE
命令。BGSAVE
命令可以在后台异步地进行快照操作,快照的同时服务器还可以继续响应来自客户端的请求。执行BGSAVE
后Redis
会立即返回OK表示开始执行快照操作,如果想知道快照是否完成,可以通过LASTSAVE
命令获取最近一次成功执行快照的时间,返回结果是一个时间戳,如:
redis> LASTSAVE
( integer) 1423537869
因为 BGSAVE
命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis
服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE
命令执行期间,服务器处理 SAVE
,BGSAVE
,BGREWRITEAOF
这三个命令的方式会和平时有所不同。(其中,BGREWRITEAOF
命令是AOF重写命令,文章后面会进行介绍)
在BGSAVE
命令执行期间,SAVE
命令和 BGSAVE
命令都会被服务器拒绝,服务器禁止 SAVE
命令和 BGSAVE
或者 两个 BGSAVE
命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个 rdbSave
调用,防止产生竞争条件。
在BGSAVE
命令执行期间也不能执行 BGREWRITEAOF
命令:
- 如果
BGSAVE
命令正在执行,那么客户端发送的BGREWRITEAOF
命令会被延迟到BGSAVE
命令执行完毕之后执行。 - 如果
BGREWRITEAOF
命令正在执行,那么客户端发送的BGSAVE
命令会被服务器拒绝
因为BGSAVE
和 BGREWRITEAOF
命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是性能方面的考虑,这两个子进程都需要执行大量的磁盘写入操作,所以,要避免二者同时执行。
3、执行 FLUSHALL
命令
当执行FLUSHALL
命令时,Redis
会清除数据库中的所有数据。需要注意的是,不论清空数据库的过程是否触发了自动快照条件,只要自动快照条件不为空,Redis
就会执行一次快照操作。例如,当定义的快照条件为当 1 秒内修改 10000 个键时进行自动快照,而当数据库里只有一个键时,执行FLUSHALL
命令也会触发快照,即使这一过程实际上只有一个键被修改了。
当没有定义自动快照条件时,执行 FLUSHALL
则不会进行快照。
4、执行复制时
当设置了主从模式时,Redis
会在复制初始化时进行自动快照。即使没有定义自动快照条件,并且没有手动执行过快照操作,也会生成 RDB
快照文件。
RDB文件结构
1、RDB
文件的开头是 REDIS
部分,长度为 5 字节,保存着 “REDIS”五个字符,通过五个字符,程序可以在载入文件时,快速检查所载入的文件是否是 RDB 文件。
2、db_version
长度为4字节,它的值是一个字符串标识的整数,这个整数记录着 RDB 文件的版本号,比如“0006”代表RDB文件版本为第六版
3、databases
包含着0个或任意多个数据库,以及各数据库中的键值对数据:
- 如果服务器的数据库状态为空,那么这个部分也为空,长度为0字节
- 如果服务器数据库状态为非空,那么这个部分也为非空,根据数据库所保存键值对的数量、类型和内容不同,这个部分的长度也会有所不同
4、EOF
常量的长度为1字节,这个常量标志着RDB文件正文内容的结束,当读入程序遇到这个值时,它知道所有数据库的所以键值对都已经载入完毕了
5、check_num
是8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对 REDIS
,db_version
,databases
,EOF
四个部分的内容进行计算得出的,服务器在载入 RDB 文件时,会将载入数据所计算出的校验和与 check_sum
所记录的校验和进行对比,以此来检查RDB文件是否有出错或损坏的情况出现。
AOF
除了 RDB 持久化功能外,Redis
还支持了AOF(Append Only File)持久化功能,与 RDB 持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis
服务器所执行的写命令来记录数据库状态的。
服务器在启动时,可以通过载入和执行 AOF
文件中保存的命令来还原服务器关闭之前的数据库状态。
AOF持久化实现
AOF
持久化功能的实现可以分为命令追加(append),文件写入,文件同步(sync)三个步骤。
命令追加
当 AOF
持久化功能处于打开状态时,服务器在执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf
缓冲区的末尾。
文件写入与同步
Redis
服务器进程是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像 serverCron 函数这样需要定时运行的函数。
为了提高文件的写入效率,在现代操作系统中,当用户调用write
函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。
这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。
为此,系统提供了fsync
和fdatasync
两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。
AOF文件的载入与数据还原
因为AOF
文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF
文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。
Redis
读取AOF
文件并还原数据库状态的详细步骤如下:
- 创建一个不带网络连接的伪客户端:因为
Redis
的命令只能在客户端上下文中执行,而载入AOF
文件时使用的命令直接来源于AOF
文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF
文件保存的写命令,伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样。 - 从
AOF
文件中分析并读取出一条写命令 - 使用伪客户端执行被读出的写命令
- 一直执行步骤2和步骤3,直到
AOF
文件中的所有写命令都被处理完毕为止
当完成以上步骤之后,AOF
文件所保存的数据库状态就会被完整地还原出来,整个过程如图所示。
AOF重写
因为AOF
持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF
文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的AOF
文件很可能对Redis
服务器、甚至整个宿主计算机造成影响,并且AOF
文件的体积越大,使用AOF
文件来进行数据还原所需的时间就越多。
如果,对一个key
进行了多次 set
操作,那AOF
文件中就要保存很多次set
命令。为了解决AOF
文件体积膨胀的问题,Redis
提供了AOF
文件重写功能,通过该功能,Redis
服务器可以创建一个新的AOF
文件代替现有的AOF
文件,新旧两个AOF
文件所保存的数据库状态相同,但新AOF
文件不会包含任何浪费空间的冗余命令,所以新的AOF
文件的体积通常比旧AOF
文件要小很多。
实际上,AOF
文件重写不需要对旧AOF
文件进行读取、分析或者写入操作,这个功能是通过读取服务器当前的数据库状态来实现的。
例如服务器为了保存一个 list 键的状态,必须在AOF
文件中写入很多set
命令,如果想要用最少的命令记录当前状态,最简单高效的方法不是读取和分析现有的AOF
文件,而是直接从数据库中读取此 list 的值,然后用一条 set
命令来代替保存AOF
文件的很多命令。
上面介绍的AOF
重写程序aof_rewrite
函数可以很好地完成创建一个新AOF
文件的任务,但是,因为这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞,因为Redis
服务器使用单个线程来处理命令请求,所以如果由服务器直接调用aof_rewrite
函数,那么在重写AOF
文件期间,服务器将无法处理客户端发来的命令请求。
所以,Redis
决定将AOF
重写程序放到子进程里执行,这样做可以同时达到两个目的:
- 子进程AOF重写期间,服务器进程可以继续处理命令请求
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,避免在使用锁的情况下,保证数据的安全性
不过,使用子进程也有一个问题需要解决,因为子进程在进行AOF
重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF
文件所保存的数据库状态不一致。
为了解决这种数据不一致问题,Redis
服务器设置了一个AOF
重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis
服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF
缓冲区和AOF
重写缓冲区
这也就是说,在子进程执行AOF重写期间,服务器进程需要执行以下三个工作:
(1) 执行客户端发来的命令
(2) 将执行后的写命令追加到AOF缓冲区
(3) 将执行后的写命令追加到AOF重写缓冲区。
这样操作可以保证:
- AOF缓冲区的内容会定期被写入和同步到
AOF
文件,对现有AOF
文件的处理工作会照常进行 - 从创建子进程开始,服务器执行的所有写命令都会被记录到
AOF
重写缓冲区里
当子进程完成AOF
重写工作后,会向父进程发送一个信号,父进程在接收该信号后,会调用一个信号处理函数,并执行以下工作:
- 将
AOF
重写缓冲区中的所有内容写入到AOF
文件,这时新AOF
文件所保存的数据库状态将和服务器当前数据库状态一致 - 对新的
AOF
文件进行改名,原子地覆盖现有的AOF
文件,完成新旧两个AOF
文件的替换
AOF
文件后台重写过程
这也是 BGREWRITEAOF
命令的实现原理。
RDB与AOF对比
RDB优势:
- RDB文件紧凑,全量备份,适合用于进行备份和灾难恢复
- 生成RDB文件时,Redis会fork()一个子进程处理所有保存工作,主进程不需要进行任何磁盘IO操作
- RDB在恢复大数据集时比AOF恢复速度快
RDB劣势:
- 当快照持久化时,会开启子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应过来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。
AOF优势:
- 可以保护数据不丢失,一般AOF会每隔1s,通过一个后台线程执行一次 fsync 操作,最多丢失1s的数据
- AOF文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损
- AOF文件即使过大时,出现后台重写操作,也不会影响客户端读写
- AOF文件的命令通过非常可读的方式进行记录,非常适合做灾难性的误删除的紧急恢复
AOF劣势:
- 对于同一份数据来说,AOF文件通常比RDB数据快照文件更大
- AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为 AOF 一般会配置成每秒 fsync 一次
小结
命令 | RDB | AOF |
---|---|---|
启动效率 | 数据集大时效率高 | 数据集小时效率高 |
文件体积 | 小 | 大 |
恢复速度 | 快 | 慢 |
数据安全性 | 丢数据 | 根据策略决定 |
轻重 | 重 | 轻 |
对于两种持久化方式各有利弊,通常都是二者结合使用实现持久化。
以上是关于一文搞懂Redis持久化方式RDB&AOF的主要内容,如果未能解决你的问题,请参考以下文章
Redis的持久化机制 (RDB&AOF&混合模式)