RDB持久化
RDB文件是经过压缩的二进制文件,可以通过使用了save和bgsave命令主动地生成RDB文件,两者有以下不同:
- save命令会阻塞主进程的,直到RDB文件命令请求完成。
- bgsave命令会让主进程派生出一个子进程,由子进程生成RDB文件,而主线程可以继续处理客户端的命令请求。
与生成RDB文件不同的是,载入RDB文件是无法通过客户端来控制的,只用当服务器启动的时候,会自动检测RDB文件并且加载。
值的一提的是:因为AOF文件的更新频率通常比RDB文件的更新频率高,所有当服务器开启了AOF持久化功能时,服务器启动会优先使用AOF文件来还原数据库状态。如果未开启,则是默认RDB文件还原数据库状态。
服务器在载入RDB文件时,整个期间会处于阻塞的状态,无法接受客户端命令,直到工作完成。
RDB的自动间隔保存
Redis支持配置服务器自动保存RDB文件,在redis.conf配置如下:
save 900 1
save 300 10
save 60 10000
以上配置的意思是:
- 服务器在900秒内对数据库进行至少1次修改
- 服务器在300秒内对数据库进行至少10次修改
- 服务器在60秒内对数据库进行至少10000次修改
在满足上面的一个条件,Redis服务器会执行bgsave命令,fork主进程得到子进程,生成RDB文件。
服务器是如何判断达到生成RDB文件的条件?
答:在redis中维持着一个redisServer结构体,其中一些属性起到作用:
struct redisServer {
// 记录了保存条件的数组
struct saveparam *saveparams;
// 修改计数器
long long dirty;
// 上一次执行保存的时间
time_t lastsave;
}
struct saveparam {
// 秒数
time_t seconds;
// 修改数
int changes;
}
saveparams是一个数组,其中存储着redis.conf中配置的三个秒数时间属性值(seconds)900、300、60,对应着时间段内的修改值changes:
saveparams[0] | saveparams[1] | saveparams[2] |
---|---|---|
seconds 900 | seconds 300 | seconds 60 |
changes 1 | changes 10 | changes 10000 |
此外在RedisServer结构体中,还存在dirty(计数器)和lastsave。
- dirty是计数器,记录着服务器在上一次save和bgsave后,服务器对数据库状态进行了多少次的修改。
- lastsave属性是一个时间戳,记录了服务器上一次成功执行save命令或者bgsave的时间。
当服务器成功执行了一个数据库修改命令后,dirty计数器就会加一。Redis服务器中serverCron()方法默认每100毫秒执行一次检查,如果满足条件就执行bgsave。
RDB文件结构
REDIS | db_version | databases | EOF | check_sum |
---|
REDIS:是文件最开头的"REDIS"五个字符,通过这五个字符可以判断载入的文件是否RDB文件。
db_version:长度为4个字节,它的值是一个字符串表示的整数,记录着RDB文件的版本号。
databases:包含所有数据库,以及各个数据库中的键值对数据。
EOF:长度为1字节,该常量标志着RDB文件正文内容结束。
check_sum:RDB文件的校验和,由前四个值计算出来。
AOF持久化
与RDB文件通过保存数据库中键值对来记录数据库状态不同,AOF持久化是通过记录数据库的写命令来记录数据库状态的。
AOF持久化的实现
AOF持久化功能的是实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
命令追加
当AOF处于开启状态时,服务器在执行完一个写命令之后,会以协议的格式追加到服务器状态的aof_buf缓存区的末尾:
struct redisServer {
// AOF缓冲区
sds aof_buf;
}
这里体现了redis中使用sds(简单动态字符串)作为缓冲区的用法。
AOF文件的写入和同步
首先,redis的服务器进程是一个事件循环,每次在处理完文件事件后可能会执行写命令,使得一些内容被追加到aof_buf缓冲区中,所以服务器每次在结束一个事件循环后,都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。
flushAppendOnlyFile函数会根据配置的appendsync选项的值来进行写入和同步AOF文件的时机。
appendsync选项的值 | flushAppendOnlyFile函数的行为 |
---|---|
always | 将aof_buf缓冲区中的所有内容写入到内存缓冲区并同步到AOF文件。 |
everysec | 将aof_buf缓冲区中的所有内容写入到内存缓冲区中,如果距离上一次同步AOF文件的时间超过一秒,那么进行同步,且同步工作给子线程做。 |
no | 将aof_buf缓冲区中的所有内容写入到内存缓冲区中,但不进行同步,何时同步到AOF文件,由操作系统决定。 |
AOF文件的载入与数据还原
因为,AOF文件中包含了重建数据库所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件中的所有写命令,就可以还原redis服务器关闭之前的所有状态。
Redis读取AOF文件并还原数据库状态的详细步骤如下:
- 创建一个不带网络的伪客户端(fake client):因为redis的命令只能在客户端执行。
- 从AOF文件中分析并读取出一条写命令。
- 使用伪客户端执行被读出的写命令。
- 重复执行2,3,直到命令被处理完毕。
AOF重写
AOF重写的根本目的就是解决AOF文件过大的问题,随着服务器运行,AOF文件追加的内容越来越多,导致AOF文件体积越来越大,会导致使用AOF文件恢复数据的时间过长。
AOF重写会重新生成新的AOF文件替代旧的文件,但是重写的AOF文件中不会包含任何浪费空间的冗余命令,所以新生成的文件体积通常会比旧文件的体积小得多。
AOF文件重写的实现
AOF重写时并不会对旧AOF文件进行任何解析,而是有点像RDB持久化,读取数据库的键对应的值,不过重写不是和RDB一样保存键值对,而是以写入数据的命令的形式来进行记录数据。例如:
# 存在键值对 name : zhang
set name zhang # 以写入数据的形式来记录数据
因为aof_rewrite()函数生成的新文件只包含还原当前数据库状态所必须的命令,所以新AOF文件不会浪费任何硬盘空间。
AOF后台重写
上面介绍重写使用的aof_rewrite()函数可以很好的完成创建新的AOF文件的任务,但是该函数会阻塞redis主进程。所以这里介绍后台重写。
Redis将AOF重写程序放到子进程中执行,这样可以达到两个目的:
- 子进程在进行AOF重写的期间,主进程可以继续接受客户端的请求。
- 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。
使用子进程的方式虽然可以避免服务器进程的阻塞,但是也会带来数据不一致的问题。
为了解决数据不一致的问题,redis设置了一个AOF重写缓冲区,当子进程在进行AOF重写工作时,服务器还会继续执行写入命令,此时,服务器会向AOF缓冲区和AOF重写缓冲区写入写命令。
当AOF重写任务完成时,子进程会向父进程发送一个完成信号,父进程接收到信号后,将AOF重写缓冲区中的所有内容写到新AOF文件中,然后完全的AOF重写文件会改名,并原子地覆盖现有的AOF文件。
网友评论