数据持久化
- 1、RDB持久化
-
- [1.1 RDB文件的创建和载入](#1.1 RDB文件的创建和载入)
- [1.2 自动间隔保存](#1.2 自动间隔保存)
- [1.3 RDB文件结构](#1.3 RDB文件结构)
- 2、AOF持久化
-
- [2.1 AOF写入](#2.1 AOF写入)
- [2.2 AOF载入](#2.2 AOF载入)
- [2.3 AOF重写](#2.3 AOF重写)
如有侵权,请联系~
如有错误,也欢迎批评指正~
本篇文章大部分是来自学习《Redis设计与实现》的笔记
1、RDB持久化
RDB持久化功能所生成的RDB文件是一个经过压缩的文件,通过RDB文件可以还原数据。
1.1 RDB文件的创建和载入
生成RDB文件有两个命令:
- save命令:阻塞Redis服务器进程,直到RDB文件创建完成之前不能处理任何其他命令
- bgsave命令:创建一个子进程,由子进程负责RDB文件的创建,redis父进程正常处理其他命令
这两种方式底层都是调用rdbSave函数。
针对于RDB的载入,没有明确的命令。而是服务器在启动的时候就会自动检测RDB文件,自动载入。RDB文件的载入是通过rdbLoad函数完成,在载入的过程正,服务器一直是处于阻塞状态。
当redis正在执行bgsave命令的时候,不会阻塞服务器执行其他命令。但是不包含save、bgsave、bgrewriteaof命令。当redis正在执行bgsave命令的时候,如果服务端又收到了:
- save命令:save命令会被拒绝,父子进程都会调用rdbsave方法,防止产生竞争
- bgsave命令:bgsave命令也会被拒绝,也会同时调用rdbsave方法
- bgrewriteaof命令:bgrewriteaof命令会被延迟到bgsave命令执行完执行。如果redis正在执行bgrewriteaof命令,收到bgsave命令会被服务器拒绝。bgrewriteaof和bgsave命令不能同时执行是因为创建两个子进程,存在大量的磁盘读写,影响性能。
1.2 自动间隔保存
除了使用命令保存RDB文件,配置文件中也会有相关的配置。默认配置:
c
save 900 1 # 900 秒内至少有 1 个键被修改时触发快照
save 300 10 # 300 秒内至少有 10 个键被修改时触发快照
save 60 10000 # 60 秒内至少有 10000 个键被修改时触发快照
只要满足一个条件就会触发bgsave进行自动保存。上述配置对应于redisServer结构体中的:
c
struct redisServer {
...
struct saveparam *saveparams; // 动态数组,存储所有 save 配置项
// 修改计数器
long long dirty;
//上一次执行保存的时间
time_t lastsave;
...
};
struct saveparam {
time_t seconds; // 时间间隔(秒)
int changes; // 键修改次数
};
saveparam数组用来存储上述自动间隔保存RDB的配置数据。
dirty记录自从上次save或者bgsave命令之后服务器【所有数据库】进行修改的次数,lastsave记录上次save或者bgsave命令成功执行的时间。
这个是由serverCron函数【默认100ms执行一次】进行扫描,通过saveparam和dirty、lastsave一起来判断是否需要触发bgsave进行RDB保存。(serverCron函数不止是进行判断是不是需要进行持久化,还包括判断过期键)
1.3 RDB文件结构

当服务器读取到db_number之后,就会切换到相应的数据库。db_number会根据数据库号的大小占用不同的大小。
每个键值对的结构如下,如果这个键没有设置过期时间,则没有前两个字段:
在RDB文件存储数据的时候,只会存储真实的数据,不会存储redisObject这种元数据。
2、AOF持久化
与RDB持久化【将键值对数据保存到RDB文件】不同,AOF持久化是将写入命令按照请求协议格式resp保存到AOF文件中。
2.1 AOF写入
数据每次写入都会存储到aof_buf缓存中。
c
struct redisServer {
...
// 持久化相关
char *rdb_filename; // RDB 文件路径
int aof_state; // AOF 状态
sds aof_buf; // AOF 缓冲区,存储到这里
....
}
服务端进程其实就是一个事件loop循环, 这个循环中包含:文件事件【执行请求并返回数据】、时间事件【如定时任务serverCron函数】。
在结束一个事件之前,都会调用flushAppendOnlyFile函数【决定是不是将aof缓存中的数据写入、同步到AOF文件中】。伪代码如下:
flushAppendOnlyFile同步的时机取决于服务器中appendfsync配置:always、everysec、no等配置。
这三种策略只是区分数据同步fsync到磁盘的时机,而写入命令是每次执行完都会将命令写入到aof_buf中和操作系统的缓存中,不区分上述配置。
2.2 AOF载入
服务器启动之后,会创建伪客户端【不带有网络连接的redis客户端】,因为redis命令需要在客户端的上下文中执行,并且载入的命令是来自于AOF文件,而不是网络连接,所以创建一个不带有网络连接的伪客户端。
然后从AOF文件中一条一条命令读取出来,伪客户端进行执行。
2.3 AOF重写
随着程序不断的运行,AOF文件会越来越大,占用磁盘空间并且当服务器重启进行数据加载的时候,AOF文件的载入也会需要很长时间。所以AOF重写就很有必要。
AOF重写其实就是将某一时刻的所有数据库的所有数据dump按照redis命令进行输出。例如
c
rpush list "A" "B"
rpush list "C"
rpush list "D" "E" "F"
lpop list
rpop list
rpush list "G" "H"
// 如果此刻进行AOF重写,就会将上述执行的结果汇总为一条执行语句【不严谨,如果数据量太大可能分为多条语句,受这个参数影响REDIS.AOF_REWRITE_ITENS_PER_CM 】
// 重写AOF文件之后,AOF文件中只会记录这一条数据rpush list "B" "C" "D" "E" "G" "H" 而不是上述六条执行语句
AOF文件在重写的时候,会挨个遍历所有数据库,遍历每个数据库的每个键值对,并且还会过滤掉过期的键。
AOF重写是在子进程中执行的,并且使用了写时复制。在进行AOF重写期间,redis在进行执行写入命令的时候,除了执行命令、将命令写入到AOF缓冲区之外,还需要写入到AOF重写缓冲区中。
等AOF重写将所有数据库中的数据写完之后,子进程会给父进程一个信号,然后父进程:
- 将AOF重写缓冲区中的命令写入到刚刚新的重写的AOF文件中,保证数据的一致性。
- 原子性的将新的重写的AOF文件替换为原AOF文件。