Redis是一个键值对数据库服务器;
服务器的非空数据库以及其中的键值对统称为“数据库状态”;
Redis是内存数据库,为防止进程退出数据消失,必须将数据库状态持久化到磁盘文件中;
Redis提供RDB持久化功能,可将内存中的数据库状态保存到磁盘中;
RDB持久化既可以手动执行,也可以根据配置服务器的配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中;
RDB持久化功能生成的RDB文件是一个经过压缩的二进制文件,服务器在启动时通过加载RDB文件还原数据库状态;
RDB文件的创建与载入
SAVE命令、BGSAVE命令都可用于生成RDB文件;
SAVE命令会阻塞服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间不能处理任何命令请求;
BGSAVE命令会派生出一个子进程,由子进程在后台负责创建RDB文件,服务器进程继续处理命令请求;
创建RDB文件的实际工作是底层的rdb.c/rdbSAVE函数,两个命令会以不同的方式调用该函数;(大致流程:①SAVE命令直接调用该函数进行RDB文件的生成;②BGSAVE命令,则先fork一个子进程,通过子进程调用该函数创建RDB文件,结束后向服务器进程发送信号,服务器进程阻塞处理该信号)
RDB文件的载入工作是在Redis服务器启动时自动执行的,没有专门的命令(服务器在启动时检测到RDB文件时,便自动将其载入)
注:AOF文件的更新频率通常比RDB文件的更新频率高,所以若服务器同时开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态;只有在AOF持久化处于关闭状态时,服务器才会使用RDB文件来还原数据库状态;
SAVE命令执行时的服务器状态
执行SAVE命令的进程将会被阻塞从而无法处理任何命令请求;
BGSAVE命令执行时的服务器状态
BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITAOF三个命令的方式会与平时不同;
- BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝(服务器进制SAVE命令和BGSAVE命令同时执行是为了避免父进程和子进程同时执行两个rdbSave的调用,防止产生竞争条件)
- BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,同时执行两个BGSAVE命令也会产生竞争条件;
- BGREWRITEAOF和BGSAVE两个命令不能同时执行:
- 若BGSAVE命令正在执行,则客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后再执行;
- 若BGREWRITEAOF命令正在执行,则客户端发送的BGSAVE命令会被服务器拒绝;
BGSAVE与BGREWRITEAOF命令实际工作虽然都由子进程执行,但并无冲突,不同是执行只是出于性能方面的考虑;
RDB文件载入时的服务器状态
服务器载入RDB文件期间,会一直出于阻塞状态,直到载入工作完成;
自动间隔性保存
BGSAVE命令可以在不阻塞服务器进程的情况下执行,因此Redis允许用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令;
save选项设置了多个保存条件,但只要其中任意一个被满足,服务器就会执行BGSAVE命令,一般情况下,服务器配置默认的save选项:
- save 900 1 ——服务器在900秒内对数据库进行了一次修改
- save 300 10 ——服务器在300秒内对数据库进行了十次修改
- save 60 10000 ——服务器在60秒内对数据库进行了一万次修改
Notes:save选项都保存在服务器状态(redisServer)中的saveparams数组中,每个数组元素记录了一个save选项设置的值;
设置自动保存条件
Redis服务器启动时,用户可通过指定配置文件 或者 传入启动参数的方式设置save选项;
若用户没有手动设置该选项值,则服务器使用配置文件中默认的自动保存条件;
dirty计数器和lastsave属性
服务器状态维护一个dirty计数器以及lastsave属性:
- dirty计数器记录了距离上一次成功执行SAVE或者BGSAVE命令后,服务器对数据库状态进行了多少次修改(增删改操作);
- lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE或者BGSAVE命令的时间;
检查保存条件是否满足
Redis的服务器周期性函数serverCron默认每隔100ms就会执行一次,该函数用于对正在运行的服务器进行维护,其中一项工作就是检查save选项所设置的保存条件是否已经满足;
程序会遍历并检查saveparams数组中的所有保存条件,只要有任意一个条件被满足,就会执行BGSAVE命令;
RDB文件结构
注:“常量”使用大写标识,“变量、数据”使用小写标识;
RDB文件最开头是REDIS部分,该部分长度为5字节,保存着“REDIS”五个字符。
载入RDB文件时,通过校验文件头的这五个字来确认载入的是否为RDB文件;
db_version长度为4字节,其值是一个字符串表示的整数,该整数记录了RDB文件的版本号;
databases部分包含着0个或者任意多个数据库,以及数据库中的键值对数据;
EOF常量的长度为1字节,标识RDB文件的正文内容结束;
check_sum为8字节的无符号整数,保存着一个校验和;该校验和是程序通过对前四部分内容的计算得出的;服务器载入RDB文件时,会通过计算前四部分得出的校验和与文件尾部的check_sum值比较来检查文件是否有损坏或出错的情况;
databases部分
RDB文件的databases部分可以保存任意多个非空数据库;
每个非空数据库在RDB文件中都可以保存为SELECTDB、db_number、key_value_paris三个部分
- SELECTDB常量长度为1字节,当读入程序遇到该值时,将知晓即将读入一个数据库号码;
- db_number保存着一个数据库号码,根据号码大小不同,该部分长度可以为1字节、2字节、5字节;(当程序读入db_number部分之后,服务器会自动调用SELECT命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中)
- key_value_pairs部分保存了数据库中所有键值对数据,若键值对带有过期时间,则过期时间也会和键值对保存在一起;
key_value_pairs部分
RDB文件中的每个key_value_pairs部分都保存了一个或以上数量的键值对,若键值对带有过期时间,则过期时间也会保存在内;
不带过期时间的键值对在RDB文件中由TYPE、key、value三部分组成:
TYPE记录了value的类型,长度为1字节;TYPE的一些列常量代表了对象类型或底层编码,当服务器读入RDB文件中的键值对数据时,程序会根据TYPE的值来决定如何读入和解释value的数据;
Key和value分别保存了键值对的键对象和值对象:
-
- key总是一个字符串对象;
- value根据底层保存的数据类型以及内容长度的不同,保存value的结构和长度也会有所不同;
带有过期时间的键值对:
EXPIRETIME_MS常量的长度为1字节,作用为告知程序接下来将要读入的是一个以毫秒为单位的过期时间;
ms是一个8字节长度的带符号整数,记录着一个以毫秒为单位的UNIX时间戳,该时间戳就是键值对的过期时间;
value的编码
RDB文件中的每个value部分都保存了一个值对象,每个值对象的类型都由与之对应的TYPE记录,根据类型的不同,value部分的结构、长度也会不同;
- 字符串对象
若TYPE的值为REDIS_RDB_TYPE_STRING,则value保存的对象类型为字符串类型,其编码可以分为REDIS_ENCODING_INT或REDIS_ENCODING_RAW;
若字符串对象的编码为REDIS_ENCODING_INT,则说明对象中保存的是长度不超过32位的整数:
ENCODING的值可以是REDIS_RDB_ENC_INT8、REDIS_RDB_ENC_INT16、REDIS_RDB_ENC_INT32三个中的一个,分别代表RDB文件使用8位、16位、32位来保存整数值interger;
若字符串对象的编码为REDIS_ENCODING_RAW,则说明对象保存的是字符串值,根据字符串长度不同,有压缩和不压缩两种方式保存该字符串:
- 若字符串长度小于等于20字节,则该字符串值会被原样保存;
- 若字符串长度大于20字节,则该字符串会被压缩后再保存;
注:该条件是在服务器打开了RDB文件压缩功能的情况下进行,若关闭该功能,则字符串值总是以无压缩的方式保存字符串值;
无压缩存储方式:
len部分保存了字符串长度的,string部分保存了值本身;
压缩过后存储:
REDIS_RDB_ENC_LZF常量标志字符串已经经过LZF算法压缩、compressed_len保存着压缩过后字符串的长度、origin_len保存着原字符串长度、compressed_string保存着被压缩之后的字符串;
- 列表对象
若TYPE的值为REDIS_RDB_TYPE_LIST,则value保存的是REDIS_ENCODING_LINKEDLIST编码的列表对象;
list_length记录了列表的长度,即列表保存了多少个项;
item部分为列表项,每个列表项都是一个字符串对象;
- 集合对象
若TYPE的值为REDIS_RDB_TYPE_SET,则value保存的是REDIS_ENCODING_HT编码的集合对象,结构:
set_size为集合的大小,记录了集合保存了多少个元素;
elem代表集合的元素,每个元素都是一个字符串对象
- 有序集合对象
若TYPE的值为REDIS_RDB_TYPE_ZSET则value保存的是一个有序集合对象,其底层编码为REDIS_ENCODING_SKIPLIST,结构如下;
sorted_set_size记录了有序集合的大小(即该有序集合保存了多少元素);
element为元素项,又分为成员和分值两部分,前者为字符串对象,后者为double类型的浮点数(程序在保存double分值时会先将分值转换成字符串对象,再使用保存字符串对象的方式保存该分值),结构如下:
- 哈希表对象
若TYPE的值为REDIS_RDB_TYPE_HASH,则value保存的是一个哈希表对象,其底层编码为:REDIS_ENCODING_HT;
hash_size记录了哈希表的大小(即该哈希表中保存了多少个键值对);
key_value_pair则为具体的数据项(键值对),键和值都为字符串对象;
- INTSET编码的集合
若TYPE的值为REDIS_TYPE_SET_INTSET,则value保存的是一个整数集合对象,RDB文件保存该对象的方法是,先将整数集合转换为字符串对象,然后将该字符串对象保存到RDB文件里面;
- ZIPLIST编码的列表、哈希表、有序集合
若TYPE的值为REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST、REDIS_RDB_TYPE_ZSET_ZIPLIST,则value保存的是一个压缩列表对象,①将压缩列表转换成一个字符串对象、②将转换的字符串对象保存在RDB文件中;
分析RDB文件
使用od命令来分析Redis服务器产生的RDB文件,该命令可以用给定的格式转存并打印输入文件;如,参数“-c”可以以ASCⅡ编码的方式打印输入文件,给定参数“-x”可以以十六进制的方式打印输入文件;
具体使用:od -c xxx.rdb
重点总结:
- RDB文件用于保存和还原Redis服务器数据库状态;
- SAVE命令由服务器进程直接执行,该命令会阻塞服务器进程;
- BGSAVE命令由子进程执行,所以不会阻塞服务器进程;
- 服务器状态中会保存所有用save选项设置的保存条件,当任一保存条件被满足时,服务器会自动执行BGSAVE命令;
- RDB文件是一个经过压缩的二进制文件,由多个部分组成;
- 对于不同类型的键值对,RDB文件会使用不同的方式来保存;