- 前言:Redis是内存数据库,程序进程意外崩溃时,内存中的数据就会丢失,所以Redis提供了RDB和AOF两种把数据持久化到硬盘的方式,Redis启动时会读取保存的文件恢复数据。
- 简单说RDB会定期生成一个全量数据的压缩二进制文件保存,而AOF则是一个文件不停写入Redis执行语句,各有优缺点,后面会具体分析。
- Redis4.0以后提供了一种混合持久化
aof-use-rdb-preamble
,将RDB数据写到AOF文件中,原先AOF依然写入,既保证速度又保证数据安全。
10.1 RDB文件的创建与载入
- bgsave和save两个命令可以实现手动触发RDB持久化,通过rdb.c/rdbSave函数完成:
- save会阻塞Redis服务器进程
- bgsave会创建子进程,不会阻塞Redis服务器
- Redis启动时会检测RDB文件自动载入:
- AOF更新频率比RDB高,如果开启AOF功能会优先使用AOF文件恢复数据,否则才会使用RDB文件
10.1.1 SAVE命令执行时的服务器状态
- save命令会阻塞Redis服务器,只有在执行完save命令后重新开始接收请求,才会继续处理命令
10.1.2 BGSAVE命令执行时的服务器状态
- BGSAVE执行期间,客户端发送SAVE命令会被拒绝,避免竞争
- BGSAVE执行期间,BGSAVE命令也会拒绝
- BGSAVE执行期间,BGREWRITEAOF命令会被推后,BGREWRITEAOF执行时BGSAVE也会推后,不拒绝但不能同时执行。BGREWRITEAOF是AOF版的异步持久化命令,逻辑上不会有冲突,但为了避免大量磁盘写入所以不能同时执行。
10.1.3 RDB文件载入时的服务器状态
- 阻塞
10.2 自动间隔保存
- 通过配置文件的save选项可以自动触发BGSAVE命令,例如:
save 900 1
save 300 10
save 60 10000
- 满足以上三个中的任意一个条件,就会执行BGSAVE:
- 900秒内至少修改过1次
- 300秒内至少修改过10次
- 60秒内至少修改过10000次
10.2.1 设置保存条件(自动保存的内部实现)
- redisServer结构的saveparam属性:
struct redisServer{
//...
//记录了保存条件的数组
struct saveparam *saveparam
//...
}
struct saveparam{
//秒数
time_t seconds;
//修改数
int changes;
}

10.2.2 dirty计数器和lastsave属性
- dirty记录上次执行BGSAVE和SAVE之后,数据库进行了多少次修改,每次修改数据dirty会递增。
- lastsave记录上次执行BGSAVE和SAVE的时间
struct redisServer{
//...
// 修改计数器
long long dirty;
// 上一次执行保存的时间
time_t lastsave;
//...
}
10.2.3 检查保存条件是否满足
- 之前提到过周期函数serverCron,默认每隔100毫秒执行一次,其中就有关于save的任务:
- 通过lastsave计算当前时间和上次自动执行BGSAVE时间间隔
- 通过lastsave和dirty遍历配置条件,如果满足就执行BGSAVE
10.3 RDB文件结构
-
RDB存储总体结构如下:
RDB文件结构.png
- 第一层(整体):
- REDIS:一个二进制常量,用于识别RDB文件,没有其他特殊意义
- db_version:rdb文件的版本,书中版本是6,意思是第六版的RDB文件结构
- database:存储具体数据的位置,下个小结展开
- EOF:结束标志
- check_sun:8字节无符号整数,前面的部分求和,检验文件是否出错
- 第二层(以db0和3有数据为例):
- 如果Redis所有库都没有数据,这部分就为空
- 第三层(单个db):
- SELECTDB:1字节常量,表示后面是数据库编号
- db_number:具体数据库编号,读取RDB到这时Redis会切换到相应编号的库
- key_value_pairs:库里具体的key value
- 第四层(key_value_pairs内部):
- EXPIRETIME_MS:常量,表示后面的是这个键值对的过期时间,如果没设置过期,EXPIRETIME_MS和ms都是空
- ms:Unix时间戳,代表超时时间
- TYPE:常量,代表value的存储类型一共9种,下个小节说明
- key:字符串,具体对象的key
- value:value根据TYPE不同存储的结构也不同,下面具体分析每种value存储的形式
10.3.3 value的编码(关联第八章):
- 字符串对象(
TYPE
常量是REDIS_RDB_TYPE_STRING
):
- 和对象底层实现一样,RDB存储时底层编码
encoding
也会不同,都体现在value
内部。第八章提到字符串有三种编码REDIS_ENCODING_INT、REDIS_ENCODING_RAW、REDIS_ENCODING_EMBSTR
,我在看书时没发现embstr的说明,两种本质都是SDS,所以猜测都算作raw了,当然这个不影响后续概念理解,总体分为 int 和 raw 两种。 - 数字(REDIS_ENCODING_INT):value结构为
encoding + integer
,这种类型不会超过32位,integer为具体数据,encoding根据数据长度分为REDIS_RDB_ENC_INT8
、REDIS_RDB_ENC_INT16
、REDIS_RDB_ENC_INT32
- 字符串(REDIS_ENCODING_RAW):浮点或整形数字过长或者字符串会用REDIS_ENCODING_RAW,当字符串大于20字节时存储RDB数据会压缩(redis.config的
rdbcompression
默认开启)。- 无压缩:结构是
len + string
长度 + 数据原样保存,这种结构里就省略了encoding部分 - 压缩时:结构是
REDIS_RDB_ENC_LZF + compressed_len + origin_len + compressed_string
,encoding默认REDIS_RDB_ENC_LZF
,compressed_len
压缩后长度,origin_len
原数据长度,compressed_string
压缩后的数据
- 无压缩:结构是
- 列表对象(
REDIS_RDB_TYPE_LIST
):
- 列表有两种编码linkedlist和ziplist,这里默认linkedlist,ziplist后面单独说明,元素都是字符串,只会有
REDIS_ENCODING_LINKEDLIST
一种,结构为list_lenth + item1_len + item1_str + item2_len + item2_str ...
,list_lenth
表示元素个数,len
代表字符串长度,*_str
是具体数据
- 集合对象(
REDIS_RDB_TYPE_SET
):
- 默认编码为ht,
set_size + elem1_len + elem1_str + elem2_len + elem2_str ...
,名字换一下,结构和属性代表的意思一样。
- 哈希表对象(
REDIS_RDB_TYPE_HASH
)
- 默认编码为ht,
hash_size + key1_size + key1_str + value1_size + value1_str + key2_size...
,其中hash_size是键值对个数,后续key + value循环到结尾也很好理解。
- 有序集合对象(
REDIS_RDB_TYPE_ZSET
)
- 默认编码spiplist,有序集合和上面结构的区别只是需要记录一个分值
sorted_set_size + member1 + score1 + member2 + score2...
,其中每个member和score前面也都有一个计数这里就不啰嗦了
- INTSET编码的集合(
REDIS_RDB_TYPE_SET_INTSET
)
- 集合对象的第二种编码实现,和REDIS_RDB_TYPE_SET相同,只是读入时把字符串对象,转成了整数集合对象。
- ZIPLIST编码的集合(
REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_ZSET_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST
)
- 有三种基本对象底层用到了ZIPLIST,列表、哈希表和有序集合。读取时会根据TYPE不同生成相应的基本类型。
网友评论