redis设计与实现——RDB持久化
由于Redis是内存数据库,在服务器进程退出时,服务器状态也会丢失不见,因此Redis提供了RDB持久化功能,可以帮助把内存中的数据库状态保存到磁盘里面,避免数据丢失。
RDB持久化既可以手动执行,也可以服务器配置定期执行,执行后会生成一个经过压缩的二进制RDB文件。
RDB文件的创建与载入
有两个Redis命令可以生成RDB文件——SAVE和BGSAVE,前者会阻塞Redis服务器进程,直到创建完RDB文件,后者则是fork出一个子进程来负责创建RDB文件。
在redis/src/rdb.c中存在实际创建RDB文件的函数rdbSave(),SAVE命令和BGSAVE命令都会以不同方式调用这个函数。
SAVE命令:
1 | void saveCommand(client *c) { |
redis 的事件循环中会去检测redisServer的saveparams字段,判断是否执行BGSAVE,在执行完之后,子进程调用_exit()退出,避免因为父进程正在对文件进行操作而子进程直接回写文件缓冲区。
1 | int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) { |
无论是SAVE还是BGSAVE,最终都需要调用rdbSave完成工作。
1 | int rdbSave(char *filename, rdbSaveInfo *rsi) { |
由此可见,rdbSave的操作主要分为两步:
- 先将数据写到一个临时文件——tmp-%d.rdb;
- 调用原子性的重命名操作;
自动间隔保存
对于BGSAVE命令,Redis支持用户可以通过制定配置文件或者传入启动参数的方式设置save选项。
- save 900 1:服务器在900秒之内,对数据库进行了至少一次修改;
redis支持多RDB配置,满足任意一个就可以触发BGSAVE。在redisServer结构体中,存在serverparams字段记录了save条件。该字段结构有两个field:时间和修改次数。
1 | struct saveparam { |
Redis的服务器周期性操作函数serverCron默认每100ms执行一次,其中一项工作就是检查save选项设置的保存条件是否满足。除了需要检查是否满足在规定时间内操作数据库的次数,还要检查上一次bgsave是否成功,如果不成功的话,需要等待CONFIG_BGSAVE_RETRY_DELAY秒,默认是5秒。
1 | int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) { |
RDB文件结构
本节主要介绍RDB的文件结构,具体的代码实现:
1 | int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) { |
因此RDB的文件结构可以总结为五个部分:
REDIS | db_version | Databases | EOF | check_sum |
---|---|---|---|---|
REDIS | 0009 | kv内容 | 255 | 8字节无符号整数 |
其中DataBase部分会保存多个非空数据库,总结可以分为三个部分,1字节长的标示码,整数的db序列号和键值对
RDB_OPCODE_SELECTDB | db_number | kv对
1 | int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) { |
键值对的保存结构是通过函数rdbSaveKeyValuePair()实现的,并且带有过期时间的键值对和不带有的都混在一起保存。其中如果有过期时间,则通过开头的RDB_OPCODE_EXPIRETIME_MS进行标示。至于保存类型则有其中,都是1字节长。key都是字符串对象。
1 |
|
结构就是:
RDB_OPCODE_EXPIRETIME_MS | ms | TYPE | key | value
或者:
TYPE | key | value