目录
Redis 是一个键值对数据库服务器,服务器中通常包含着任意个非空数据库,而每个非空数据库中有可以包含任意个键值对,为了方便起见,我们将服务器中的非空数据库以及它们的键值对统称数据库状态。
Redis 是内存数据库,它将自己的数据库状态储存在内存里,如果不想办法将储存在内存中的数据库状态保存到磁盘里,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。为了解这个问题,Redis 提供了两种持久化方式—— RDB(Redis DataBase) 和 AOF(Append Only File) ,这可以将 Redis 在内存中的数据库状态保存到磁盘里。
下面详细介绍一下 RDB 持久化,希望能帮助到其他小伙伴~ 关于 AOF 持久化详解 可参考下一篇。
1.1 RDB 概述
RDB 其实就是把数据库状态以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。
RDB 持久化是指可以将某个时间点上的数据库状态保存到一个 RDB 文件中。RDB 文件是经过压缩的二进制文件,默认的文件名为dump.rdb。因为 RDB 文件是保存在磁盘,所以不会丢失。
1.2 RDB 持久化执行方式
RDB 持久化可以手动执行(SAVE、BGSAVE),也可以根据服务器配置选项定期执行(自动化)。
1. 执行SAVE 命令时,会阻塞当前 Redis 服务器,执行 SAVE 命令期间,Redis 不能处理其他命令,直到 RDB 文件创建完毕为止。
redis> SAVE //等待 直到RDB文件创建完毕
OK
2. 执行 BGSAVE 命令时,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体操作是 Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。
redis> BGSAVE //派生子进程,并由子进程创建RDB文件
Background saving started
基本上 Redis 内部所有的 RDB 操作都是采用 BGSAVE 命令。
3. 根据 redis.conf 配置里的 save m n 定时执行(用的是BGSAVE),只要其中任意一个条件被满足,服务器就会执行 BGSAVE 命令。
服务器的 save 选项设置默认条件:
- save 900 1 服务器在900秒内,对数据库进行了至少1次修改
- save 300 10 服务器在300秒内,对数据库进行了至少10次修改
- save 60 10000 服务器在60秒内,对数据库进行了至少10000次修改
只要满足以上任意条件服务器就会执行 BGSAVE 命令。如果想禁用快照保存的功能, 可以通过注释掉所有 "save" 配置达到,或者在最后一条 配置后添加:save ""。
因为 BGSAVE 命令的保存工作是由子进程执行的,所以在子进程创建 RDB 文件的过程中,Redis服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE 命令执行期间,服务器处理 SAVE、BGSAVE、BGREWRITEAOF三个命令的方式和平时有所不同:
首先,在 BGSAVE 命令执行期间,客户端发送的 SAVE 命令会被服务器拒绝,服务器禁止 SAVE 命令和 BGSAVE 命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个 rdbSave 调用,防止产生竞争条件;
其次,在 BGSAVE 命令执行期间,客户端发送的 BGSAVE 命令会被服务器拒绝,因为同时执行两个 BGSAVE 命令也会产生竞争条件;
最后,BGREWRITEAOF 和 BGSAVE 两个命令不能同时执行:①如果 BGSAVE 命令正在执行,那么客户端发送的 BGREWRITEAOF 命令会被延迟到 BGSAVE 命令之执行完毕之后执行;②如果 BGREWRITEAOF 命令正在执行,那么客户端发送的 BGSAVE 命令会被服务器拒绝。
因为 BGREWRITEAOF 和 BGSAVE 两个命令的实际工作都是由子进程执行,所以两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑——并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作,这怎么想都不会是一个好主意。
1.3 RDB 文件的创建与载入
创建 RDB 文件的实际工作由 rdb.c/rdbSave 函数完成,SAVE 命令和 BGSAVE 命令会以不同的方式调用这个函数,通过一下伪代码可以明显看出这两个命令的区别:
def SAVE () :
# 创建RDB文件
rdbSave()
def BGSAVE () :
# 创建子进程
pid = fork()
if pid == 0 :
# 子进程负责创建RDB文件
rdbSave()
#完成之后向父进程发送信号
signal_parent()
elif pid > 0 :
# 父进程继续处理命令请求,并通过轮询等待子进程的信号
handle_request_and_wait_signal()
esle :
# 处理出错情况
handle_fork_error()
和使用 SAVE 命令或者 BGSAVE 命令创建 RDB 文件不同,RDB 文件载入工作是在服务器启动时自动执行,所以 Redis 并没有专门用于载入 RDB文件的命令,只要 Redis 服务器在启动时检测到 RDB 文件存在,就会自动载入它。服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。
以下是 Redis 服务器启动时打印的日志记录,其中第二条日志 DB loading from disk:…就是服务器在成功载入 RDB 文件之后打印的:
$ redis-server
[7379] 06 Aug 21:07:01.270 # Server started, Redis version 2.9.11
[7379] 06 Aug 21:07:01.289 * DB loading from disk:0.018 seconds
[7379] 06 Aug 21:07:01.289 * The server is now ready to accept connections on port 6379
载入 RDB 文件的实际工作有 rdb.c/rdbLoad 函数完成。这个函数和 rdbSave 函数之间的关系可以用图 1-3-1 表示。
1.4 自动化执行原理
当Redis 服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置 save 选项,接着,服务器程序会根据 save 选项所设置的保存条件,设置服务器状态 redisServer 结构的 saveparams 属性:
struct redisServer {
// ...
//记录了保存条件的数组
struct saveparam * saveparams;
// ...
};
saveparams 属性是一个数组,数组中的每个元素都是一个 saveparam 结构,每个 saveparam 结构都保存了一个 save 选项设置的保存条件:
struct saveparam {
// 秒数
time_t seconds;
// 修改数
int changes;
};
比如说,save选项的值为默认条件,那服务器状态中的 saveparams 数组如图 1-4-1 所示。
除了 saveparams 数组之外,服务器状态还维持着一个 dirty 计时器,以及一个 lastsave 属性:
dirty 计数器记录距离上一次成功执行 SAVE 命令或者 BGSAVE 命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作);
lastsave 属性是一个 UNIX 时间戳,记录服务器上一次成功执行 SAVE 命令或者 BGSAVE 命令的时间。
struct redisServer {
// ...
// 修改计数器
long long dirty
// 上一次执行保存时间
time_t lastsave
// ...
};
当服务器成功执行一个数据库修改命令之后,程序就会对 dirty 计数器进行更新:命令修改了多少次数据库,dirty 计数器的值就增加多少。
例如,如果我们为一个字符串键设置值:
redis> SET message "hello"
OK
那么程序就会将 dirty 计数器加1。
如果我们想一个集合键增加三个新元素:
redis> SADD database Redis MongoDB MariaDB
(integer) 3
那么程序会将 dirty 计数器的值增加3。
如图 1-4-2 展示了服务器中包含的dirty 计数器 和 lastsave 属性。
Redis 的服务器周期性操作函数 serverCron 默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务进行维护,它的其中一项工作就是检查 save 选项所设置的保存条件是否已经满足,如果满足的话就执行 BGSAVE 命令。
参考:
《Redis设计与实现》 黄建宏 著
https://blog.csdn.net/xinbumi/article/details/89742930
https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc