redis · 24 3 月, 2021 0

Redis RDB持久化实现原理

RDB持久化

Redis数据状态: 保存在Redis数据库中的数据被称作数据状态。

 

RDB文件的创建与载入

有两个Redis命令可以用于生成RDB文件, 一个是SAVE, 另一个是BGSAVE

 

RDB文件创建

  • SAVE命令会阻塞Redis服务器进程, 知道RDB文件创建完毕为止, 在服务器进程阻塞期间, 服务器不能处理任何命令请求.

  • BGSAVE派生出一个子进程, 然后由子进程创建RDB, 服务器进程继续处理命令请求.

 

RDB文件载入

RDB文件的载入工作时在服务器启动时自动执行的, 所以redis并没有专门用于载入rdb文件的命令, 只要redis服务器在启动时检测到rdb文件存在, 它就会自动载入RDB文件.

  • 如果服务器开启了AOF持久化功能, 那么服务器会优先使用AOF文件来还原数据库状态

  • 只有在AOF持久化功能处于关闭状态时, 服务器才会使用RDB文件来还原数据库状态。

 

SAVE, BGSAVE, BGREWRITEAOF

  • 在BGSAVE命令执行期间, 客户端发送的SAVE命令会被服务器拒绝, 服务器禁止SAVE命令和BGSAVE命令同时执行时为了避免父进程和子进程同时执行两个rdbSave调用, 防止产生竞争条件

  • BGSAVE命令执行期间, 客户端发送的BGSAVE命令会被服务器拒绝, 因为同时执行两个BGSAVE命令也会产生竞争条件.

  • BGREWRITEAOFBGSAVE两个命令不能同时执行:

    • 如果BGSAVE命令正在执行, 那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行

    • 如果BGREWRITEAOF命令正在执行, 那么客户端发送的BGSAVE命令会被服务器拒绝。

 

RDB文件载入时的服务器状态

服务器在载入RDB文件期间, 会一直处于阻塞状态, 直到载入工作完成为止.

 

自动间隔性保存

redis允许用户通过设置服务器配置的save选项, 让服务器每隔一段时间自动执行一次BGSAVE.

  • save 900 1

  • save 300 10

  • save 60 10000

只要满足以下三个条件中的任意一个, BGSAVE命令就会被执行

  • 服务器在900秒之内, 对数据进行了一次修改

  • 服务器在300秒之内, 对数据库进行了至少10次修改

  • 服务器在60秒之内, 对数据库进行了至少10000次修改。

 

设置保存条件

redis主要通过save设置选项, 默认的配置选项如上所示: 接着, 服务器程序会根据save选项所设置的保存条件, 社会服务器状态redisServer结构的saveparams属性:

struct redisServer {  
    // 记录了保存条件的数据  
    struct saveparam *saveparams;
}

其中saveparams属性时一个数组, 数据中的每个元素都是一个saveparam结构, 每个saveparam结构都保存了一个save选项设置的保存条件:

struct saveparam {
  // 秒数
  time_t seconds;

  // 修改数
  int changes;
}

dirty计数器和lastsave属性

除了saveparams数组之外, 服务器状态还维持着一个dirty计数器, 以及一个lastsave属性:

  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后, 服务器对数据库状态进行了多少次修改(包括写入, 删除, 更新等操作)

  • lastsave属性是一个UNIX时间戳, 记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间.

检查保存条件是否满足

  • 遍历保存的所有的保存条件

  • 计算从上次成功执行BGSAVEsave到现在所经历的秒数

  • BGSAVE命令执行完成之后, 则lastsave设置为当前时间, 并将dirty清零

RDB文件结构

RDB文件结构, 整体包含了五部分:

  • REDIS

  • db_version

  • databases

  • EOF

  • check_sum

 

REDIS

RDB文件最开头是REDIS部分, 这部分的长度为5字节, 保存着”REDIS”五个字符串, 程序在加载的时候, 能够快速检查所在如的文件是否RDB文件.

 

db_version

db_version长度为4字节, 他的值是一个字符串表示的整数, 这个整数记录了RDB文件的版本号. 例如: 0006代表RDB文件版本为第六版

 

databases

databases部分包含着零个或任意多个数据库, 以及各个数据库中的键值对数据:

  • 如果服务器的数据库状态为空, 那么这个部分也为空, 长度为0字节

  • 如果服务器的数据库状态为非空, 那么这部分也为非空, 根据数据库保存键值对的数量, 类型和内容不同, 这部分的长度也会有所不同。

  •  

EOF

常量长度为1字节, 这个常量标志着RDB文件正文内容的结束, 当读入程序遇到这个值的时候, 它知道所有数据库的所有键值对已经载入完毕。

 

check_sum

check_sum是一个8字节长的无符号整数, 保存着一个校验和, 这个校验和是程序通过对REDIS,db_version,databases,EOF四个部分的内容进行计算得出的。

 

分析RDB文件

对于RDB文件的分析, 我们可以使用od命令行来分析

  • -c可以以ascii编码的方式打印输入文件

  • -x参数可以以十六进制的方式打印输入文件

 

不包含任何键值对的RDB文件

redis> FLUSHALL​
redis> SAVE​

od -c dump.rdb​

0000000   R   E   D   I   S   0   0   0   6 377 334 263   C 360   Z 334 
0000020 362   V 
0000022

根据之前学习的RDB文件结构知识, 当一个RDB文件没有包含任何数据库数据时, 这个RDB文件将由一下四个部分组成:

  • 五个字节的”REDIS”

  • 五个字节的版本号(db_version)

  • 一个字节的EOF常量

  • 八个字节的检验和(check_sum)

从上面例子中, 0006版本号之后, 一个字节377代表EOF常量, 然后334 263 C 360 Z 334 362 V八个字节则达标RDB文件的校验和.

包含字符串键的RDB文件

redis> FLUSHALL 
redis> SET msg "HELLO" 
$ od -c dump.rdb 

0000000   R   E   D   I   S   0   0   0   6 376 \0 \0 003   m   s   g 
0000020 005   H   E   L   L   O 377 \n   < 342 005   <   A 217   4 
0000037

 

一个数据库被保存到RDB文件时, 这个数据库将由以下三个部分组成:

  • 一个一字节长的特殊值SELECTDB

  • 一个长度可能为一字节,两字节,五字节的数据库号码(db_number)

  • 一个或以上数量的键值对(key_value_pairs)

通过上面的输出:

  • 376代表SELECTDB常量

  • 之后的\0代表整数0, 表示被保存的数据库为0号数据库

  • 直到377为止, RDB文件包含有\0 003 m s g 005 H E L L O

    • 没有过期时间的键值对由类型(TYPE),键(key),值(value)三部分组成

      • 其中类型的长度为一字节

      • 建和值都是字符串对象, 并且字符串在未被压缩前, 都是义字符串长度为前缀, 后跟字符串内容本身的方式来存储的。

    • \0就是字符串类型的TYPEREDIS_RDB_TYPE_STRING

    • 003是键MSG的长度值

    • 005则是HELLO的长度

包含带有过期时间的字符串键的RDB文件

redis> FLUSHALL redis> SETEX msg 10086 "HELLO" 
redis> SAVE

​$ od -c dump.rdb 0000000   R   E   D   I   S   0   0   0   6 376 \0 374   m   e 254 035 l 001 \0 \0 \0 003   m   s   g 005   H   E   L   L   O 377 005   ' 234 214   _ 245 254 350 '

 

一个带有过期时间的键值对将由以下部分组成:

  • 一个一字节长的EXPIRETIME_MS特殊值

  • 一个八字节长的过期时间(ms)

  • 一个一字节长的类型(TYPE)

  • 一个键key和一个值(value)

根据这些特征, 可以得出RDB文件各个部分的意义:

  • REDIS 0006: RDB文件标志和版本号

  • 376 \0: 切换到0号数据库

  • 374: 代表特殊值EXPIRETIME_MS

  • m e 254 035 l 001 \0 \0 代表八字节长的过期时间

  • \0 003 m s g: \0表示这是一个字符串键, 003是键的长度, MSG是键

  • 005 H E L L O: 005是值的长度, HELLO是值

  • 377: 代表EOF常量

  • 005 ' 234 214 _ 245 254 350: 代表八字节长的检验和

包含一个集合键的RDB文件

redis> FLUSHALL 
redis> SADD LANG "C" "JAVA" "RUBY" 
redis> SAVE​

$ od -c dump.rdb R   E   D   I   S   0   0   0   6 376 \0 002 004   l   a   n g 003 004   R   U   B   Y 004   J   A   V   A 001   c 377 224 363   E   7 363 \t   j   {

 

  • REDIS006: RDB文件标志和版本号

  • 376 \0: 切换到0号数据库

  • 002 004 l a n g:

    • 002 是常量REDIS_RDB_TYPE_SET(这个常量实际值为整数2), 表示这是一个哈希表编码的集合键

    • 004 表示键的长度

    • LANG: 键的名称

  • 003:集合的大小

  • 004 R U B Y: 集合的第一个元素

  • 004 J A V A: 集合的第二个元素

  • 001 C: 集合的第三个元素

  • 377: 代表常量EOF

  • 224 363 E 7 363 \t j {: 代表校验和

关于分析RDB文件的说明

因为REDIS本身带有RDB文件检查工具redis-check-dump