Redis是一个内存数据库,数据库的状态都存储在内存中,如果服务器进程退出,那么所有的服务器状态都会丢失。Redis提供了RDB持久化功能,可以将内存中数据库的状态保存到磁盘中,避免因为意外进程退出导致的数据库状态丢失。
1. RDB文件
- RDB存储对象长度定义,用来表示存储当前对象需要多少字节
宏定义 | 值 | 描述 |
---|---|---|
RDB_6BITLEN | 0 | 00XXXXXX,长度存储在后8bits中 |
RDB_14BITLEN | 1 | 01XXXXXX XXXXXXXX 长度存储在后14bits中 |
RDB_32BITLEN | 0x80 | 10000000 [32 bit] 长度存储在后32bits中 |
RDB_64BITLEN | 0x81 | 10000001 [64 bit] 长度存储在后64bits中 |
RDB_ENCVAL | 3 | 11OBKIND 表示一个特殊编码对象,用REDISRDS_ENC*指定 |
RDB_LENERR | UINT64_MAX | 表示长度错误 |
- RDB对象编码类型,RDB_ENCVAL定义长度情况下, 用该编码类型表示
宏定义 | 值 | 描述 |
---|---|---|
RDB_ENC_INT8 | 0 | 8位有符号整数 |
RDB_ENC_INT16 | 1 | 16位有符号整数 |
RDB_ENC_INT32 | 2 | 32位有符号整数 |
RDB_ENC_LZF | 3 | LZF压缩字符串 |
- RDB对象存储类型,将Redis中的对象存储到RDB文件中保存的类型
宏定义 | 值 | 描述 |
---|---|---|
RDB_TYPE_STRING | 0 | 字符串对象 |
RDB_TYPE_LIST | 1 | 列表对象 |
RDB_TYPE_SET | 2 | 集合对象 |
RDB_TYPE_ZSET | 3 | 有序集合对象 |
RDB_TYPE_HASH | 4 | 哈希对象 |
RDB_TYPE_ZSET_2 | 5 | 有序集合对象版本2,score用二进制存储 |
RDB_TYPE_MODULE | 6 | 模块对象 |
RDB_TYPE_MODULE_2 | 7 | 带有注释的模块对象,用于解析而不生成 |
- RDB对象存储类型的编码方式
宏定义 | 值 | 描述 |
---|---|---|
RDB_TYPE_HASH_ZIPMAP | 9 | ZIPMAP编码的哈希对象,已不再使用 |
RDB_TYPE_LIST_ZIPLIST | 10 | ZIPLIST编码的列表对象 |
RDB_TYPE_SET_INTSET | 11 | INTSET编码的集合对象 |
RDB_TYPE_ZSET_ZIPLIST | 12 | ZIPLIST编码的有序集合对象 |
RDB_TYPE_HASH_ZIPLIST | 13 | ZIPLIST编码的哈希对象 |
RDB_TYPE_LIST_QUICKLIST | 14 | QUICKLIST编码的列表对象 |
- RDB操作码,保存和加载类型时使用
宏定义 | 值 | 描述 |
---|---|---|
RDB_OPCODE_AUX | 250 | AUX操作 |
RDB_OPCODE_RESIZEDB | 251 | 需要resize操作 |
RDB_OPCODE_EXPIRETIME_MS | 252 | 过期毫秒数 |
RDB_OPCODE_EXPIRETIME | 253 | 过期秒数 |
RDB_OPCODE_SELECTDB | 254 | 选择db操作 |
RDB_OPCODE_EOF | 255 | 结束操作 |
1.1 RDB文件结构
REDIS | VERSION | DATABASE | EOF | CHECKNUM |
---|---|---|---|---|
“REDIS” | 版本号 | 数据区域 | 结束标识 | 校验和 |
RDB文件以“REDIS”开头,后紧接着4字节的版本号。
database数据区域存储着一个或多个特定格式编码的数据库。
EOF是RDB正文结束。
check_num是校验和,通过前4部分计算得出。
1.2 database数据区域部分
一个RDB文件可能存在多个非空数据库,通过OPCODE_SELECTDB操作码可以选择数据库对象,格式
RDB_OPCODE_SELECTDB | db_number | key_value_pairs |
---|---|---|
选择数据库操作码 254 | 数据库编号 | 所有的键值对数据 |
1.3 键值对数据区域
RDB文件中每个键值对数据区域都保存着一个或多个键值对,如果带有过期时间,那么过期时间也会被保存。
RDB_OPCODE_EXPIRETIME | ms | TYPE | key | value |
---|---|---|---|---|
过期时间操作码 | 过期时间 | 键类型 | 键 | 值 |
2. RDB文件的创建
SAVE命令和BGSAVE命令可以创建RDB文件。由于Redis是单进程的,所以SAVE命令创建RDB文件会阻塞Redis服务器,这种情况下,服务器不能处理任何命令请求。BGSAVE则会生成一个子进程用于创建RDB文件,父进程则进行处理命令请求。
注:在同一时间内,只能存在一个BGSAVE子进程。
RDB文件创建由rdb.c/rdbSave函数实现:
创建临时文件temp-pid.rdb,用于保存RDB内容
将数据库内容按照一定编码格式写入文件
1)计算校验和
2)存储redis版本
3)遍历服务器数据库并写入到文件,内存内容到RDB需要按照一定的编码格式和规则
4)存在lua脚本也需要备份脚本
5)存储EOF操作码
6)存储校验和
修改临时文件名为RDB文件名
记录服务端操作信息
BGSAVE命令同样会调用rdbSave函数,区别是在fork出的子进程中执行该函数。
3. RDB文件的加载
在Redis服务器启动时,会根据配置的选项进行初始化,若开启了RDB功能,且未开启AOF功能,则会从RDB文件中加载数据到内存中。加载过程中,Redis也是处于阻塞状态,不能处理其他服务指令。
AOF文件更新频率通常高于RDB,所以如果开启了AOF功能,服务器会首先使用AOF文件还原数据库状态。
RDB文件的加载由rdb.c/rdbLoad函数实现:
打开RDB文件
开始加载
初始化Redis输入输出缓存对象
读取RDB文件中的数据到内存
1)设置计算校验和函数
2)设置加载读或写的最大字节数
3)读取9字节,这9字节存储着redis的版本信息,并判断版本是否正确
4)循环读取RDB文件,按照写入RDB文件的编码方式进行读取
5)Redis版本大于5则需要进行校验计算
关闭文件
加载完成
4. 源码剖析
- RDB创建实现
1 | int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) { |
- RDB读取实现
1 | int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) { |
- RDB创建一个对象
1 | ssize_t rdbSaveObject(rio *rdb, robj *o) { |
- RDB读取一个读写
1 | robj *rdbLoadObject(int rdbtype, rio *rdb) { |