Redis是内存数据库,它的数据状态储存在内存里面的,所以如果不将数据库状态保存在磁盘里面,那么服务器进程一但退出,服务器中的数据就会消失不见,为了解决这个问题,Redis提供了持久化功能,分别是RDB持久化和AOF持久化。
RDB持久化
RDB持久化的功能简单来说就是将一个时间节点上的数据库状态保存到一个RDB文件中,这个操作既可以手动执行,也可以根据服务器配置的选项来定期的执行。
RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。
RDB文件的创建和载入
Redis有两个命令用来生成RDB文件,一个是SAVE,另外一个是BGSAVE。
SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进行阻塞期间,服务器是不能处理任何其他的命令请求的。
与SAVE命令不同的是,BGSAVE命令会派生一个子进程,然后由子进程来创建RDB文件。
当Redis创建了RDB文件之后,那么什么时候Redis通过这个RDB文件进行恢复数据库状态?
RDB文件的载入是在服务器启动的时候自动执行的,所有Redis并没有专门的载入RDB文件的命令,只要服务器启动,就会检测RDB文件的存在,它就会自动的载入RDB文件。
值得一提的是,因为AOF文件(AOF持久化方式产生的文件)的更新频率比RDB文件的更新频率更加的高,所以如果数据库开启了AOF的持久化功能,那么服务器会优先使用AOF文件来还原数据库的状态。只有在AOF持久化功能处于关闭的时候,服务器才会使用RDB文件来还原数据状态。
持久化命令执行时的服务器状态
由前面提到的,当服务器执行SAVE命令的时候,Redis服务器会被阻塞,所以当SAVE命令执行的时候服务器无法执行接收到的其他命令。
而当执行BGSAVE命令的时候,由于BGSAVE命令是派生一个子进程来执行SAVE命令,所以在BGSAVE命令执行期间,服务器仍然可以执行其他的命令请求,但仍然还是有一些需要注意的地方。
- 首先是在BGSAVE命令期间,为了避免父进程和子进程同时执行两个rdbSAVE调用,所以在BGSAVE命令执行期间,会拒绝接收SAVE命令。
- 同样在BGSAVE命令执行期间,也会拒绝其他的BGSAVE命令,因为这样也会产生竞争。
- 最后BGREWITEAOF命令和BGSAVE两个命令不能同时执行。如果BGSAVE命令在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行之后执行,如果BGREWRITEAOF命令在执行,那么服务器就会拒绝BGSAVE命令。
自动间隔性保存
因为BGSAVE命令可以在不阻塞服务器进程的情况下执行,所以我们可以通过配置文件,配置save选项,让服务器每个一段时间就可以自动执行BGSAVE命令。
例如,我们向服务器提供以下配置
- save 900 1 (服务器900秒内,对数据库至少进行一次修改)
- save 300 10 (服务器300秒内,对数据库至少进行10次修改)
- save 60 10000 (服务器10000秒内,对数据库至少进行60次修改) 那么只要满足上面的三个条件中的任何一个条件,那么服务器就会执行BGSAVE命令。
服务器会根据save选项设置的保存条件,设置服务器状态redisServer结构的saveparam属性:
struct redisServer {
......
// 记录了保存条件的数组
struct saveparam *saveparams;
}
saveparams属性是一个数组,数组中的每个元素都是saveparam结构,每个saveparam结构都保存这个save选项设置的命令
struct saveparam {
// 秒数
time_t seconds;
// 修改数
int changes;
}
除了saveparams数组之外,服务器还维持一个dirty计数器,以及一个lastsave属性
- dirty计数器记录的是上次SAVE命令或者是BGSAVE之后,服务器对数据库状态进行了多少次的修改
- lastsave属性是一个unix时间戳,记录的是上一次成功执行BGSAVE或者SAVE命令的时间。
struct redisServer {
// 修改计数器
long dirty;
// 上一次执行保存时间
time_t lastsave;
}
当服务器成功的执行一个对数据库修改的命令,dirty计数器就会进行更新。
AOF持久化
与RDB持久化通过保存数据库中的键值对不同,AOF持久化是通过保存Redis服务器所执行的命令来记录数据库的状态的。然后服务器启动的时候,可以通过执行AOF文件中的命令来还原服务器关闭之前的数据库状态。
AOF持久化的实现
当AOF开启之后,服务器在执行完一个写命令之后会以协议的格式将这个命令追加到服务器的aof_buf缓冲区的尾部。
AOF文件的写入和同步
Redis的服务器进程就是一个时间循环,在这个循环的过程中文件事件负责接收客户端的命令请求,然后回复客户端,然后时间事件就负责执行一些定时需要循环的函数。
当一些写命令被追加到aof_buf之后,服务器会调用一次flushAppendOnlyFile函数,考虑是否需要将缓冲区里面的内容写入和保存到AOF文件里面。
伪代码:
def eventLoop() {
while True :
// 处理文件事件,接收命令和回复命令
// 这个时候可能会有新的命令被追加到aof_buf缓冲区里面
processFileEvents();
// 处理时间事件
processTimeEvent();
// 考虑是否需要将aof_buf中的数据写入到AOF文件里面
flushAppendOnlyFile();
}
而这个flushAppendOnlyFile函数是根据服务器配置的appendfsync选项值来决定的。
| appendfsync选项值 | flushAppendOnlyFile函数行为 |
|---|---|
| always | 会将缓冲区的所有内容写入到AOF文件中 |
| everysec | 将缓冲区的内容写入到AOF文件中,如果上次同步AOF文件的时间距离现在超过一秒那么就会再次进行同步 |
| no | 将缓冲区的内容写入到AOF文件中,但是并不对AOF文件进行同步,至于何时进行同步由操作系统来决定 |
如果用户没有设置appendfsync这个选项,那么服务器会默认设置everysec。
AOF的持久化效率和安全性。当appendfsync为always的时候,服务器每个事件循环都需要将缓冲区的内容写入到AOF文件,并且同步到缓冲区当中,所以这个选项的最安全的,就算是故障停机,但是AOF持久化文件里面也只是缺少一个事件循环中所产生的命令,但是这个选项效率也是最慢的。
而当appendfsync的值为eveysec时,服务器每个事件循环都需要将缓冲区的内容写入到AOF文件中,并且每隔一秒钟就在子线程中对AOF文件进行一次同步,从效率上讲这个模式足够快,并且就算出现故障停机了,数据库只丢失了一秒钟的数据。
但appendfsync的值为no的时,服务器在每个事件循环中都需要将缓冲区中的内容写入到AOF文件,但至于何时同步AOF文件这个需要由操作系统来进行控制,所以对于这个模式,AOF文件的写入速度很快,但是会在系统缓存中积累一定的数据,同步的时候需要较长的时间,而且在这个模式下,出现故障宕机的情况下,服务器将丢失上次AOF文件同步之后的所有写入命令数据。
AOF文件的载入和重写
当数据库想要根据AOF文件来还原服务器关闭前的数据库状态时,主要的步骤是
- 首先创建一个不带网络连接的伪客户端,因为Redis命令只能在客户端的上下文中执行,而载入AOF文件时所使用的命令就是来源自AOF文件中,而不是网络连接,所以就需要一个没有网络连接的伪客户端。
- 从AOF文件中分析并读取命令
- 使用伪客户端执行命令
- 重复上面的两个步骤直到AOF文件中的所有命令都被处理完为止
为什么需要重写?
AOF持久化通过保存被执行的写命令来记录数据库状态的,所以随着时间的推移,AOF文件中的内容会越来越多,文件的体积也会越来越大,最主要的是其他有部分的命令可能是无效,冗余的命令。所以Redis提供了AOF重写功能,来解决这个问题
重写步骤:
- 首先Redis创建一个新的AOF文件
- 然后从数据库中读取现有的键和值,然后使用一条命令区记录这个键值对
- 然后将这个命令存储到新的AOF文件中 通过这样的方式,新的AOF文件只存储了包含还原数据库状态的必须命令,所以并不会浪费任何空间,
注意的是,重写程序在处理列表,哈希表,集合,有序集合这种可能含有多个元素的键时,会先检查键所包含元素的数量,如果这个数据量超过一定的阈值的时候,就会使用多条命令来记录,而不是一条。
AOF后台重写
为了在AOF重写期间,服务器能够继续的接收命令并进行处理,AOF重写程序也是通过创建一个子进程来执行的,这样有两个好处:
- 一个就是前面讲的子进程重写期间,父进程可以继续工作
- 另外一个就是子进程拥有父进程的数据副本,使用子进程而不是线程,可以避免使用锁的情况下,保证数据的安全性
但使用AOF重写也有一个问题,那就是在重写期间,会有新的写入命令产生,这样就会造成AOF重写后的文件与数据库的状态不一致。
为了解决这个问题,Redis服务器设计一个AOF重写缓冲区,这个缓冲区专门用来存储在重写期间新追加的写入命令,当子进程重写完AOF文件之后,就会向父进程发送一个信号,然后父进程就会调用一个信号处理函数,然后执行一下操作: - 将AOF重写缓冲区的内容,写入到新的AOF文件中,这个时候就会达到新AOF文件与数据库状态一致
- 对新的AOF文件进行改名,然后原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换
- 处理完上面的操作之后,父进程就可以继续的接收处理命令,整个AOF重写过程中,只有进行信号函数处理的时候会对父进程造成阻塞,其他时候,都不会影响父进程的工作
混合持久化
通过上面的内容,读者对RDB和AOF持久化方式都有了一个清晰的了解,但实际上Redis4.0带来的一种全新的持久化方式---混合持久化
混合持久化简单来讲就是在AOF重写的时候,首先将共享的内存副本以RDB方式全量的写入到新AOF文件中,然后再将重写缓冲区中的内容增量的形式写入到新AOF文件中,最后再进行一个新旧AOF文件的替换。
这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。
缺点就是AOF里面的RDB部分是压缩格式,不再是AOF格式可读性较差。