Redis
分别提供了 RDB
和 AOF
两种持久化机制, 本章首先介绍 Redis
服务器保存和载入 RDB
文件的方法,重点说明 SVAE
命令和 BGSAVE
命令的实现方式。之后,本章会继续介绍 Redis
服务器自动保存功能的实现原理。各个组成部分,并说明这些部分的结构和含义。在本章的最后,我们将对实际的 RDB
文件进行分析和解读,将之前学到的关于 RDB
文件的知识投人到实际应用中。其中还会查看有些伪代码方便理解,本文来源 redis设计与实现,关于 redis
持久化知识比较重要,所以直接看的书,避免走弯路,以这篇文章记录一下。
RDB
持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个 RDB
文件中。所生成的 RDB
文件是一个经过压缩的二进制文件,通过该文件可以还
原生成RDB
文件时的数据库状态。
有两个 Redis
命令可以用于生成 RDB
文件,一个是 SAVE
,另一个是 BGSAVE
SAVE
命令会阻塞 Redis
服务器进程,直到 RDB
文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求BGSAVE
命令会派生一个子进程,然后由子进程负责创建 RDB
文件,服务器进程(父进程)继续处理命令请求创建 RDB
文件的实际工作由 rdb.c/rdbsave
函数完成, SAVE
命令和 BGSAVE
命令会以不同的方式调用这个函数,通过以下伪代码可以明显地看出这两个命令之间的区别:
def SAVE():
rdbSave()
def BGSAVE():
pid = fork()
if pid == 0:
# 子进程保存 RDB
rdbSave()
elif pid > 0:
# 父进程继续处理请求,并等待子进程的完成信号
handle_request()
else:
# pid == -1
# 处理 fork 错误
handle_fork_error()
RDB
文件的载入工作是在服务器启动时自动执行的,载入 RDB
文件的实际工作由 rdb.c/rdbLoad
函教完成,所以 Redis
并没有专门用于载人 RDB
文件的命令,只要 Redis
服务器在启动时检测到 RDB
文件存在,它就会自动载入 RDB
文件。
看到以上输出就是在成功载入 RDB
文件打印的,另外值得一提的是,因为 AOF
文件的更新频率通常比 RDB
文件的更新频率高,所以:
AOF
持久化功能,那么服务器会优先使用 AOF
文件来还原数据库状态。AOF
持久化功能处于关闭状态时,服务器才会使用 RDB
文件来还原数据库状态。下图是服务器载入文件时的判断流程:
前面提到过,当 SAVE
命令执行时, Redis
服务器会被阻塞,所以当 SAVE
命令正在执行时,客户端发送的所有命令请求都会被拒绝。
只有在服务器执行完 SAVE
命令、重新开始接受命令请求后,客户端发送的命令才会被处理。
因为 BGSAVE
命令的保存工作是由子进程执行的,所以在子进程创建 RDB
文件的过程中, Redis
服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE
命令执行期间,服务器处理 SAVE
、 BGSAVE
、 BGREWRITEAOF
三个命令的方式会和平时有所不同。
首先,在 BGSAVE
命令执行期间,客户端发送的 SAVE
命令会被服务器拒绝,服务器禁止 SAVE
命令和 BGSAVE
命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个 rdbSave函数
调用,防止产生竞争条件。
其次,在 BGSAVE
命令执行期间,客户端发送的 BGSAVE
命令会被服务器拒绝,因为同时执行两个 BGSAVE
命令也会产生竞争条件
最后, BGREWRITEAOF
和 BGSAVE
两个命令不能同时执行:
BGSAVE
命令正在执行,那么客户端发送的 BGREWRITEAOF
命令会被延迟到 BGSAVE
命令执行完毕之后执行。BGREWRITEAOF
命令正在执行,那么客户端发送的 BGSAVE
命令会被服务器拒绝。因为 BGSAVE
和 BGREWRITEAOF
两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑。并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写人操作,这怎么想都不会是一个好主意。
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成
这个就是利用 BGSAVE
命令,设置相关条件执行命令,例如我们 redis
一般有如下配置:
save 900 1
save 300 10
save 60 10000
以上配置的解释
struct redisServer {
// 记录保存条件的数组
struct saveparam *saveparams;
// 修改计数器
long long dirty;
// 上一次执行保存的时间
time_t lastsave;
// ....
}
struct saveparam {
// 秒数
time_t seconds;
// 修改数
int changes;
}
大概用图表示是这样
除了 saveparams
数组之外,服务器状态还维持著一个 dirty
计数器,以及一个 lastsave
属性
dirty
计数器记录距离上一次成功执行 SAVE
命令或者 BGSAVE
命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)。lastsave
属性是一个 UNIX
时间戳,记录了服务器上一次成功执行 SAVE
命令或者 BGSAVE
命令的时间。例如:
SET message "hello" # 程序此时将 dirty计数器增加1
SADD database Redis MongoDB MariaDB # 程序此时将 dirty计数器增加3
上图就展示了服务器状态中包含的 dirty
计数器和 lastsave
属性,说明如下:
dirty
计数器的值为 123 ,表示服务器在上次保存之后,对数据库状态进行了 123 次修改lastsave
属性则记录了服务器上次执行保存操作的时间戳Redis
的服务器周期性操作函数 servercron
默认每隔100毫秒就会执行一次,该函条件是否已经满足,如果满足的话,就执行 BGSAVE
命令。
以下伪代码展示了 servercron
函教检查保存条件的过程:
def serverCron():
# 遍历所有条件
for saveparam in server.saveparams:
# 计算距离上次执行保存操作有多少秒
save_interval = unixtime_now() - server.lastsave
# 如果数据库状态的修改次数超过条件所设置的次数 并且距离上次保存的时间超过条件所设置的时间 那么执行保存操作
if server.dirty >= saveparam.changes and save_interval > saveparam.seconds:
BGSAVE()
通过以上代码可以得知,程序会遍历并检查 saveparams
数组中所有保存条件,只要有任意一个条件被满足,那么服务器就会执行 BGSAVE
命令
以下展示了一个完整 RDB
文件所包含的各个部分
RDB
文件的最开头是 REDIS
部分,这个部分的长度为5字节,保存著"REDIS"
五个字符。通过这五个字符,程序可以在载入文件时,快速检查所载入的文件是否 RDB
文件。
db_version
一个四字节长的以字符表示的整数,记录了该文件所使用的 RDB
版本号。目前的 RDB
文件版本为 0009
。因为不同版本的 RDB
文件互不兼容,所以在读入程序时,需要根据版本来选择不同的读入方式。
databases
部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据:
EOF
常量的长度为1字节,这个常量标志着 RDB
文件正文内容的结束,当读入程序遇到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了
check_sum
是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对 REDIS
、db_version
、databases
、EOF
四个部分的内容进行计算得出的。服务器在载人RDB
文件时,会将载人数据所计算出的校验和与 check_sum
所记录的校验和进行对比,以此来检查 RDB
文件是否有出错或者损坏的情况出现。
从 Version 5
开始,如果在配置文件中开启 rdbchecksum yes
,会在 RDB
文件的结尾处,用 8
个字节, CRC64
计算整个文件内容的检验和。
这个是我最新拉的 redis
,数据为空,我们依次进行分析: od -c rdb.rdb
0000000 R E D I S 0 0 0 9 372 \t r e d i s
0000020 - v e r 005 6 . 0 . 9 372 \n r e d i
0000040 s - b i t s 300 @ 372 005 c t i m e 302
0000060 M 301 264 _ 372 \b u s e d - m e m 302 @
0000100 345 \f \0 372 \f a o f - p r e a m b l
0000120 e 300 \0 377 g 311 203 274 200 T 211 376
0000134
实际上这些字段是 AOF
和 RDB
通用部分的文件头内容:
REDIS
RDB
版本号redis-ver
和它的值,即 redis
版本redis-bits
和它的值,即 redis
的位数,值为32或64ctime
和它的值, RDB
文件创建时间used-mem
使用内存大小aof-preamble
和它的值,值为0或1,1表示RDB有效。但 RDB
文件头在 aof-preamble
之前多了如下三项:
repl-stream-db
在 server.master
客户端中选择的数据库repl-id
当前实例 replication ID
repl-offset
当前实例复制的偏移量RDB
文件用于保存和还原 Redis
服务器所有数据库中的所有键值对教据。SAVE
命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。BGSAVE
令由子进程执行保存操作,所以该命令不会阻塞服务器。save
选项设置的保存条件,当任意一个保存条件被潮足时,服务器会自动执行 BGSAVE
命令。RDB
文件是一个经过压缩的二进制文件,由多个部分组成。RDB
文件会使用不同的方式来保存它们。