有些面试官问: redis为什么快?候选人答: 因为它是基于内存的等balabala, 然而作为一款非关系型数据库,如果机器断电、系统重启就丢失了数据,显然是不符合数据库的标准,因此redis也需要持久化机制来将数据保存在硬盘中。
一. Redis的持久化类型
redis一共支持两种类型的持久化方式 - AOF(Append on File)和 RDB(Redis Database Backup file),从名字就可以看出,一种是增量的,一种是全量的。现网环境中,两者经常搭配使用。
1.1 AOF
1.1.1 什么是AOF,它的特点是什么?
AOF持久化是基于命令的,如执行 set key value 这样一条普通的命令,在redis服务端往内存中插入数据后,会将这条命令追加到 RedisServer对象里的aof_buf缓冲区。随后在一个IO多路复用的循环中,会将aof_buf缓冲区里的数据写入和保存到AOF文件里,完成持久化。
写入、保存 实际上是两个步骤,因此,基于这两个步骤的一致性等级,AOF持久化会有三种不同的行为,由appendfsync配置项来控制
| appendfsync取值 | 对应的行为 | 是否会丢失数据 |
|---|---|---|
| always | 写入、保存同步发生,效率最低,安全性最高 | 在一个循环中有可能丢失数据,如执行了命令但还没来得及加入aof_buff,或者加入了aof_buff还没来得及让程序运行到刷入文件的代码。但丢失的数据最少 |
| everysync | 先将aof_buf里的数据写入到aof文件中,如果上次同步距离现在超过1s,则执行保存操作,并且这个保存是由另一个线程单独执行 | 除了always情况下会丢失数据的场景,1s内服务异常也会丢失数据。是兼顾安全和效率的中和场景 |
| no | 只将aof_buf里的数据写入到aof文件中,何时同步由操作系统来决定 | 除了always情况下会丢失数据的场景,由于无法预测系统何时执行保存操作,可能大于1s,所以丢失的数据可能最多。 |
文件的写入和同步(概念)
上面提到,文件的写入和同步是两个分开的步骤,这其实是操作系统为了提高文件的写入效率而做的操作。当数据写入到文件时,操作系统通常会将写入的数据暂时保存在内核缓冲区里(注意和aof_buf不是一个东西),等到缓冲区填满,或者超过了指定的时间后,才将缓冲区里的数据写入到磁盘中。(是不是很熟悉,这种设计在计算机中随处可见,如redis的pipeline,或者mysql的批量插入)
这种设计虽然提高了效率(因为磁盘很慢),但也提高了数据丢失的可能性,因此操作系统提供了fsync和fdatasync两个命令来强制让操作系统将上述两个步骤同步进行,这也就是redis中appendfsync的原理。
综合来看,redis aof持久化可以分为以下步骤
graph TD
1.执行命令 --同步--> 2.将命令添加到aof_buf --异步--> 3.将aof_buf的所有命令添加到内核缓冲区里 --视appendfsync可同步或异步--> 4.保存到磁盘
1.1.2 什么是AOF重写?
AOF保存的是命令行,想象一下,若项目运行了100天,共产生了1000W次redis的写命令,那么AOF文件该有多大。因此redis的设计者想到了重写AOF来缩小文件这一概念。具体怎么重写呢?举个例子
- 第一天执行命令 set student xiaoming
- 第二天执行命令 set student limei
- 第三天执行命令 set student wangwu
以上是对student键进行了三次赋值,依据AOF理论,此时AOF会按顺序保存上述三条命令,重写的时候,前面两条命令会被舍弃,只留存最后一条命令。因为通过AOF还原时,前面两条是否执行,不影响最后student这个键的值是wangwu。
AOF重写就是对同一个key的多条命令,在不影响执行结果的情况下,删除无用的命令。但它不是对之前的AOF文件进行分析,然后删除。而是直接分析当前数据库里的键值对,并生成命令。如当前数据库里sds类型的键student的值就是wangwu,那重写时,redis就生成set student wangwu命令并写入新的AOF文件中。
1.1.3 AOF还原机制
当机器断电重启时,或者需要还原数据的其他场景,怎么通过AOF文件完成数据还原呢?
其实有了命令,那还原不就是将命令从头执行一遍吗?事实上redis就是这么做的。
- redis服务端启动,执行载入程序
- 创建一个不带网络链接的伪客户端
- 用伪客户端来一条一条执行命令
- 所有命令执行完毕,载入程序结束,服务端启动成功。
1.2 RDB
前面提到,RDB是全量的持久化类型,它的本质就是一个快照,像一个照相机一样,对当前redis里存的键值对咔嚓照一张相,还原时再依据相片里的内容一比一还原所有键值对。 所以RDB文件存储的是redis数据库里所有的现存的键值对。
1.2.1 RDB的特点是什么?
RDB保存的结果是一个二进制文件,redis服务器在生成RDB文件时,分为异步和同步两种类型。我们知道redis是单进程的,那在生成RDB的过程中,如果使用save命令来执行,那么主进程也会被阻塞,这是不可接受的,因此生产上需要使用bgsave 命令来执行。该命令会fork出一个子进程来完成RDB文件的生成,主进程继续执行主流程。
从上述说明看好像和redis是单进程是冲突的。实际上我们说redis是单进程指的是redis处理客户端命令是单进程,并不代表整个redis服务就只使用了一个进程。请注意区分。
和AOF持久化自动追加命令不同,RDB支持自动生成和手动生成两种。手动生成很好理解,客户端执行bgsave命令即可。
自动生成怎么理解?这涉及到redis服务端的另一个参数 save duration times. 如 save 900 1. 它的含义是,当服务器在900s之内,发生过1次键值对的增删改操作,就触发RDB。
1.2.2 RDB还原机制
和AOF一样,redis服务器在服务启动的时候,会自动检测是否存在RDB文件,若有的话,就还原该二进制数据文件,并阻塞启动直到还原完成。
需要注意的是,若redis server同时开启了AOF和RDB持久化,服务仅会选择通过AOF的方式来还原,因为它的数据完整性是最高的。RDB是对数据库的一次全量快照,它的机制决定了它无法频繁生成RDB文件。所以在AOF文件存在的情况下,优先选择AOF无可厚非。
1.3 AOF和RDB,该怎么选择?
AOF和RDB各有特点。
AOF的优点是:
- 写入时每执行一次命令就追加一条,可靠性高,性能好。
- 当AOF文件大时,会触发异步AOF重写来为文件瘦身。
AOF的缺点是:
- 若程序执行时间过长,即使通过重写,AOF文件里保存的命令也会很多,相应的,在启动时需要一条一条去执行,耗时长。
RDB的优点是:
- 对当前数据库键值对生成一次全量快照,适合做备份
- 通常来说,RDB文件比AOF文件要小一些,数据恢复时比AOF要快一些
RDB的缺点是:
- 生成的频率不高,存在数据丢失的高风险。
- 生成RDB文件时,虽然可以异步,但对redis服务器的压力会比AOF更大。
该怎么选择呢?
生产上还是建议两个持久化开关都打开,AOF用来还原数据库,而RDB用来备份数据库,比如业务上出现了脏数据,需要将整个redis还原到某个节点。
注意:redis不支持 AOF的重写和RDB的备份同时进行,虽然两者都是启动一个子进程来后台执行,但由于都是对当前数据库的键值对进行全量扫描,压力可想而知。