RDB 是什么

Redis *.rdb 是内存的二进制文件,通过 *.rdb 能够完全回复 Redis 的运行状态。

![](http://yano.oss-cn-beijing.aliyuncs.com/blog/

20220218175013.png?x-oss-process=style/yano)

RDB 文件格式

详细信息可参考:Redis RDB Dump File Format

Header

RDB 文件的头部占用 9bytes,前 5bytes 为 Magic String, 后 4bytes 为版本号

52 45 44 49 53 #"REDIS", 就像 java 的 class 文件以 0xCAFEBABE 开头一样
30 30 30 36 #RDB 版本号,30 表示‘0’,版本号为 0006=6

注意:版本号是字符串而不是整型:

snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);

RDB_VERSION 详细信息可参考:Redis RDB Version History

Body

DB Selector

FE 开头表示后跟表示 DB Selector,例如:

FE 00   #FE 表明数据库的哪个 db,此处为 db0

注意:DB Selector 长度不固定,具体的编码方式请参见后文的 Length 编码。

AUX Fields

FA 开头表示后跟 AUX Fields, 记录生成 Dump 文件的 Redis 相关信息,例如 redis-ver、redis-bits、used-mem、aof-preamble 和 repl-id 等。这些信息采用 String 编码;

注意:redis3.0 版本的 RDB 版本号为 6,redis3.2 的版本号为 7;

Key-Value

key-value 有三种格式:

  1. expire 为 second

    FD $unsigned int    #失效时间(秒),4 个字节
    $value-type #1 个字节,表明数据类型:set,map 等
    $string-encoded-key #key 值,字符串类型
    $encoded-value #value, 编码方式和类型有关
  2. expire 为 millisecond

    FC $unsigned long    #失效时间(毫秒),8 个字节
    $value-type #数据类型,1 个字节
    $string-encoded-key #key,字符串类型
    $encoded-value #value, 编码方式和类型有关
  3. 无 expire

    $value-type         #数据类型,1 个字节
    $string-encoded-key #key,字符串类型
    $encoded-value #value, 编码方式和类型有关

Footer

FF              #RDB 文件的结束
8byte checksum #循环冗余校验码,Redis 采用 crc-64-jones 算法,初始值为 0

编码算法说明

Length 编码

长度采用 BigEndian 格式存储,为无符号整数

  1. 如果以"00"开头,那么接下来的 6 个 bit 表示长度;
  2. 如果以“01”开头,那么接下来的 14 个 bit 表示长度;
  3. 如果以"10"开头,该 byte 的剩余 6bit 废弃,接着读入 4 个 bytes 表示长度 (BigEndian);
  4. 如果以"11"开头,那么接下来的 6 个 bit 表示特殊的编码格式,一般用来存储数字:
  • 0 表示用接下来的 1byte 表示长度
  • 1 表示用接下来的 2bytes 表示长度;
  • 2 表示用接下来的 4bytes 表示长度;

String 编码

该编码方式首先采用 Length 编码 进行解析:

  1. 从上面的Length 编码知道,如果以"00","01","10"开头,首先读取长度;然后从接下来的内容中读取指定长度的字符;
  2. 如果以"11"开头,而且接下来的 6 个字节为“0”、“1”和“2”, 那么直接读取接下来的 1,2,4bytes 做为字符串的内容(实际上存储的是数字,只不过按照字符串的格式存储);
  3. 如果以“11”开头,而且接下来的 6 个字节为"3", 表明采用 LZF 压缩字符串格式:

LZF 编码的解析步骤为:

  1. 首先采用Length 编码读取压缩后字符串的长度 clen;
  2. 接着采用Length 编码读取压缩前的字符串长度;
  3. 读取 clen 长度的字节,并采用 lzf 算法解压得到原始的字符串

Score 编码

  1. 读取 1 个字节,如果为 255,则返回负无穷;
  2. 如果为 254,返回正无穷;
  3. 如果为 253,返回非数字;
  4. 否则,将该字节的值做为长度,读取该长度的字节,将结果做为分值;

Value 编码

Redis 中的 value 编码包括如下类型:

其中 String 编码在前面已经介绍过,接下来逐一介绍其他的 9 种编码方式;

List

  1. 首先用 Length 编码读取 List 的长度 lsize;
  2. 采用 String 编码读取 lsize 个字符串

Set

同 List

Sorted Set

  1. 首先用 Length 编码读取 Sorted Set 的长度 zsize;
  2. 采用 String 编码读取字符串,采用 Score 编码读取分值;
  3. 循环读取 zsize 次;

Hash

  1. 采用 Length 编码读取 Hash 的大小 hsize;
  2. 采用 String 编码读取 2*hsize 的字符串,按照 key,value 的方式组装成 Map

Zipmap

用于存储 hashmap,Redis2.6 之后,该编码被废弃,转而采用 Ziplist 编码;

采用 String 编码读取整个 zipmap 字符串,hashmap 字符串的格式为:

<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>
  1. zmlen: 一个字节,Zipmap 的大小;如果>=254, 意味着 zipmap 的大小无法直接获取到,必须要遍历整个 zipmap 才能得到大小;
  2. len: 字符串长度,1 或 5 个字节长度;如果第一个字节在 0~252 之间,那么长度为第一个字节;如果为 253, 那么接下来的 4 个字节表示长度;254 和 255 是无效值;
  3. free:1 字节,表明 value 空闲的字节数;
  4. zmend:0xff, 表示 Zipmap 的结尾;

Ziplist

采用 String 编码读取整个 ziplist 字符串,字符串的格式为:

<zlbytes><zltail><zllen><entry><entry><zlend>
  1. zlbytes:4 字节无符号整数,表示 ziplist 占用的总字节数;
  2. zltail:4 字节无符号整数 (little endian), 表示尾元素的偏移量;
  3. zllen:2 字节无符号整数 (little endian), 表示 ziplist 中的元素个数,当元素个数大于 65535 时,无法用 2 字节表示,需要遍历列表获取元素个数;
  4. entry:ziplist 中的元素;
  5. zlend: 常量 (0xff), 表示 ziplist 的结尾;

entry 的格式:

<length-prev-entry><encoding><content>
  1. lenth-prev-entry: 如果第一个字节<254, 则用 1bytes 表示长度;否则则用接下来的 4bytes(无符号整数)表示长度;
  2. encoding
  • "00"开头:字符串,用接下来的 6bit 表示长度;
  • "01"开头:字符串,用接下来的 14bit 表示长度;
  • "10"开头:字符串,忽略本字节的 6bit, 用接下来的 32bit 表示长度;
  • "11000000"开头:整数,内容为接下来的 16bit;
  • "11010000"开头:整数,内容为接下来的 32bit;
  • "11100000"开头:整数,内容为接下来的 64bit;
  • "11110000"开头:整数,内容为接下来的 24bit;
  • "11111110"开头:整数,内容为接下来的 8bit;
  • "1111"开头 :整数,内容为接下来的 4bit 的值减去 1;
  1. content

    entry 内容,它的长度通过前面的 encoding 确定;

注意:元素长度、内容长度等都是采用 Little Endian 编码;

Intset

Intset 是一个整数组成的二叉树;当 set 的所有元素都是整形的时候,Redis 会采用该编码进行存储;Inset 最大可以支持 64bit 的整数,做为优化,如果整数可以用更少的字节数表示,Redis 可能会用 16~32bit 来表示;注意的是当插入一个长度不一样的整数时,有可能会引起整个存储结构的变化;

由于 Intset 是一个二叉树,因此它的元素都是排序过的;

采用 String 编码读取整个 intset 字符串,字符串的格式为:

<encoding><length-of-contents><contents>
  1. encoding:32bit 的无符号整数;可选值包括 2、4 和 8;表示 inset 中的每个整数占用的字节数;
  2. length-of-contents:32bit 无符号整数,表示 Intset 中包含的整数个数;
  3. contents: 整数数组,长度由 length-of-contents 决定;

Sorted Set in Ziplist Encoding

采用 Ziplist 编码,区别在于用两个 entry 分别表示元素和分值;

Hashmap in Ziplist Encoding

采用 Ziplist 编码,区别在于用两个 entry 分别表示 key 和 value;

实际例子

本篇文章在本地安装并启动 Redis 服务,保存一个 string 类型的字符串,save 之后查看保存的 rdb 文件的二进制。

安装、启动 Redis

下载见:Redis Download

启动 Redis server:

src/redis-server&

启动一个 Redis client:

src/redis-cli

保存字符串

127.0.0.1:6379> set name yano
OK

保存 RDB 文件

127.0.0.1:6379> save
80277:M 15 Feb 2022 10:51:07.308 * DB saved on disk
OK

在刚执行 redis-cli 的目录下,就生成了 rdb 文件,文件名是 dump.rdb。

分析 RDB 文件

使用 hexedit 命令分析 dump.rdb 文件:

hexedit dump.rdb

dump.rdb 文件内容如下:

本篇文章只是分析 rdb 文件的基本结构和格式,只保存了一个最基础的 string。(图画了一个小时)RDB 这块的 Redis 源码就不分析了,基本上都是按照这个结构来的。

参考链接

Redis 源码简洁剖析系列

最简洁的 Redis 源码剖析系列文章

Java 编程思想-最全思维导图-GitHub 下载链接,需要的小伙伴可以自取~

原创不易,希望大家转载时请先联系我,并标注原文链接。

Redis 源码简洁剖析 13 - RDB 文件的更多相关文章

  1. Redis 源码简洁剖析 11 - 主 IO 线程及 Redis 6.0 多 IO 线程

    Redis 到底是不是单线程的程序? 多 IO 线程的初始化 IO 线程运行函数 IOThreadMain 如何推迟客户端「读」操作? 如何推迟客户端「写」操作? 如何把待「读」客户端分配给 IO 线 ...

  2. Redis 源码简洁剖析 03 - Dict Hash 基础

    Redis Hash 源码 Redis Hash 数据结构 Redis rehash 原理 为什么要 rehash? Redis dict 数据结构 Redis rehash 过程 什么时候触发 re ...

  3. Redis 源码简洁剖析 06 - quicklist 和 listpack

    quicklist 为什么要设计 quicklist 特点 数据结构 quicklistCreate quicklistDelIndex quicklistDelEntry quicklistInse ...

  4. Redis 源码简洁剖析 07 - main 函数启动

    前言 问题 阶段 1:基本初始化 阶段 2:检查哨兵模式,执行 RDB 或 AOF 检测 阶段 3:运行参数解析 阶段 4:初始化 server 资源管理 初始化数据库 创建事件驱动框架 阶段 5:执 ...

  5. Redis 源码简洁剖析 16 - 客户端

    整体概述 客户端属性 套接字描述符 标志 输入缓冲区 命名及命令参数 命令的实现函数 输出缓冲区 客户端的创建与关闭 创建普通客户端 关闭普通客户端 参考链接 Redis 源码简洁剖析系列 整体概述 ...

  6. Redis 源码简洁剖析 05 - ziplist 压缩列表

    ziplist 是什么 Redis 哪些数据结构使用了 ziplist? ziplist 特点 优点 缺点 ziplist 数据结构 ziplist 节点 pre_entry_length encod ...

  7. Redis 源码简洁剖析 09 - Reactor 模型

    Reactor 模型 事件驱动框架 Redis 如何实现 Reactor 模型 事件的数据结构:aeFileEvent 主循环:aeMain 函数 事件捕获与分发:aeProcessEvents 函数 ...

  8. Redis 源码简洁剖析 10 - aeEventLoop 及事件

    aeEventLoop IO 事件处理 IO 事件创建 读事件处理 写事件处理 时间事件处理 时间事件定义 时间事件创建 时间事件回调函数 时间事件的触发处理 参考链接 Redis 源码简洁剖析系列 ...

  9. Redis 源码简洁剖析 12 - 一条命令的处理过程

    命令的处理过程 Redis server 和一个客户端建立连接后,会在事件驱动框架中注册可读事件--客户端的命令请求.命令处理对应 4 个阶段: 命令读取:对应 readQueryFromClient ...

随机推荐

  1. 硬核 - Java 随机数相关 API 的演进与思考(下)

    本系列将 Java 17 之前的随机数 API 以及 Java 17 之后的统一 API 都做了比较详细的说明,并且将随机数的特性以及实现思路也做了一些简单的分析,帮助大家明白为何会有这么多的随机数算 ...

  2. 对飞猪H5端API接口sign签名逆向实验

    免责声明 本文章所提到的技术仅用于学习用途,禁止使用本文章的任何技术进行发起网络攻击.非法利用等网络犯罪行为,一切信息禁止用于任何非法用途.若读者利用文章所提到的技术实施违法犯罪行为,其责任一概由读者 ...

  3. 《Go组件设计与实现》-netpoll的总结

    主要针对字节跳动的netpoll网络库进行总结.netpoll网络库相比于go本身的net标准库更适合高并发场景. 基础知识 netpoll与go.net库一样使用epoll这种IO多路复用机制处理网 ...

  4. 【漏洞复现】CVE-2022–21661 WordPress核心框架WP_Query SQL注入漏洞原理分析与复现

    影响版本 wordpress < 5.8.3 分析 参考:https://blog.csdn.net/qq_46717339/article/details/122431779 在 5.8.3 ...

  5. java基础04-数据类型扩展及面试题

    java基础04-数据类型扩展及面试题讲解 public class demo02 { public static void main(String[] args){ // 一.整数拓展: 进制 二进 ...

  6. 将待授权的数据库的dbowner指派给该用户

    USE 数据库goEXEC dbo.sp_changedbowner N'账号'

  7. 【刷题-LeetCode】150 Evaluate Reverse Polish Notation

    Evaluate Reverse Polish Notation Evaluate the value of an arithmetic expression in Reverse Polish No ...

  8. X架构键盘选购指南

    X架构键盘 联想 JME8002(未知) JME7597(推荐) 据说是ThinkPAD键盘的血统. Thinkapad 小红点键盘,据说腿撑容易坏 明基 明基精钢侠 海湾海贝(键程长,不累手,但不易 ...

  9. 用Cesium可视化地下设施、矿山和地质层

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 本月的CesiumJS 1.70发布引入的一项关键改进是地下数据 ...

  10. AOP-操作术语

    AOP(术语) 1,连接点 类里面哪些方法可以被增强,这些方法称为连接点 2,切入点 实际被真正增强的方法,称为切入点 3,通知(增强) (1)实际增强的逻辑部分称为通知(增强) (2)通知有多种类型 ...