逐字节讲解 Redis 持久化(RDB 和 AOF)的文件格式(一)

前言

相信各位对 Redis 的这两种持久化机制都不陌生,简单来说,RDB 就是对数据的全量备份,AOF 则是增量备份,而从 4.0 版本开始引入了混合方式,以 7.2.3 版本为例,会生成三类文件:RDB、AOF 和记录 aof 文件的元数据信息文件,如下图所示,这时的 AOF 可以看作是一种差异备份。

image-20231117142130770

接下来本文将结合具体的备份文件,通过分析其结构,从另一种角度来看两种持久化方式的差异。

RDB

首先是对 RDB 全量备份文件的解析,想要生成 RDB 文件,有两种方式,一种是手动方式:使用 save(阻塞)或者 bgsave(非阻塞)命令生成,一种是在配置文件中增加save m n(表示在 m 内,至少出现了 n 次变更就会执行 bgsave 命令)配置来实现。

下面就以一个具体的dump.rdb(在 0 号库中有一条键为 hello,值为 world 的记录)文件为例来解析其文件格式,由于 RDB 文件是二进制格式,这里使用了一个在线的十六进制编辑器进行查看:

image-20231117151039644

下文均是结合 Redis 7.2.3 版本的源码的 rdb.c 文件进行解析,对应源码地址。

0x00 Redis 版本

52 45 44 49 53 30 30 31 31,根据源码snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);可以看到这里前五位是固定值REDIS,后四位用于标识RDB的版本对应11。

0x01 辅助信息

这部分涉及数据较多,先放出源码:

if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"redis-bits",redis_bits) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"ctime",time(NULL)) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb,"used-mem",zmalloc_used_memory()) == -1) return -1;
if (rdbSaveAuxFieldStrInt(rdb, "aof-base", aof_base) == -1) return -1;

结合编辑器右侧的信息,可以发现这部分数据下图中选中的数据:

在这里插入图片描述

  1. redis-ver(Redis 版本)

    这部分对应FA 09 72 65 64 69 73 2D 76 65 72 05 37 2E 32 32 2E 33,其中开头的FA(250)代表这部分数据是 AUX 属性字段,根据源码#define RDB_OPCODE_AUX 250可以了解到。然后是09 72 65 64 69 73 2D 76 65 72,09 代表随后的 9个字节是属性名,即redis-ver,最后是05 37 2E 32 32 2E 33,其中 05 代表随后的 5 个字节是属性名对应的字段值,即 Redis 的版本号7.2.3

  2. redis-bits(位架构)

    这部分对应FA 0A 72 65 64 69 73 2D 62 69 74 73 C0 40。参考 1 可知开始的FA代表AUXOA代表随后的 10 字节是属性名,即redis-bits。但是随后的C0就不再是代表值的长度了,这里先说明C0代表后续的一个字节按照整数进行读取,对应0x40(64),即代表是 Redis 的 64位架构。下面我们再来说明为什么会有以上的区别:

    其实代表值长度的不一定只有一个字节,这里会根据前两位进行判断(C0 对应1100 0000):

    • 如果前两位是 00 ,那么后续的 6 位(可表示 0 ~ 63)就代表实际的字符串长度。

    • 如果前两位是 01,那么接下来的一个字节也会用于表示长度,加上第一个剩下的 6 位,总共 14 位(可表示0 ~ 16383)代表实际的字符串长度。

    • 如果前两位是 10,那么剩下 6 位的值如果是 0,就代表随后的 32 字节代表具体长度,如果剩下 6 位的值是 1,就代表随后的 64 字节代表具体长度。

    • 如果前两位是 11,则需要根据整个字节的值再进行判断,如果是C0就代表将随后的 1 字节表示整数,如果是 C1 就代表随后的 2 字节表示整数,如果是 C2 就代表随后的 4 字节表示整数,如果是C3就代表随后的内容是使用LZF 压缩算法处理后的内容。

  3. ctime(文件创建时间)

    这部分对应FA 05 63 74 69 6D 65 C2 44 11 57 65,参考 1 可知开始的FA代表AUX05代表随后的 5 字节是属性名,即ctime。参考 2 中解析,可知随后的C2代表后续的 4 字节即44 11 57 65表示整数,由于需要按照小端序读取,因此对应的内容是 0x65571144,即秒级时间戳,如下图所示:

    image-20231120085845280

  4. used-mem(内存使用大小)

    这部分对应FA 08 75 73 65 64 2D 6D 65 6D C2 40 15 12 00,参考 1 可知开始的FA代表AUX08代表随后的 8 字节是属性名,即used-mem。参考 3 ,可知随后的C2代表后续的 4 字节即40 15 12 00表示整数,对应的内容是 0x00121540,即 Redis 在 创建 rdb 文件前占用的内存是 1185088 字节(1.13 MB)。

  5. aof-base (是否为 aof 基准文件)

    这部分对应FA 08 61 6F 66 2D 62 61 73 65 C0 00,参考 1 可知开始的FA代表AUX08代表随后的 8 字节是属性名,即aof-base。参考 2 中解析,可知随后的C0代表后续的 1 字节即00表示整数,即该 RDB 文件不是作为 AOF 的基准文件,后文中可以看到在 AOF 中生成的 RDB 文件中该值为 1。

0x02 数据部分

FE 00 FB 01 00 00 05 68 65 6C 6C 6F 05 77 6F 72 6C 64,这部分开始对应具体的数据信息,先展示源码:

/* save all databases, skip this if we're in functions-only mode */
if (!(req & SLAVE_REQ_RDB_EXCLUDE_DATA)) {
    for (j = 0; j < server.dbnum; j++) {
        if (rdbSaveDb(rdb, j, rdbflags, &key_counter) == -1) goto werr;
    }
}

// 以下内容是 rdbSaveDb 函数内的语句

/* Write the SELECT DB opcode */
if ((res = rdbSaveType(rdb,RDB_OPCODE_SELECTDB)) < 0) goto werr;
written += res;
if ((res = rdbSaveLen(rdb, dbid)) < 0) goto werr;
written += res;
/* Write the RESIZE DB opcode. */
unsigned long long expires_size = dbSize(db, DB_EXPIRES);
if ((res = rdbSaveType(rdb,RDB_OPCODE_RESIZEDB)) < 0) goto werr;
written += res;
if ((res = rdbSaveLen(rdb,db_size)) < 0) goto werr;
written += res;
if ((res = rdbSaveLen(rdb,expires_size)) < 0) goto werr;
written += res;

可以看出这部分是遍历所有的数据库内容然后进行保存,下面再结合具体的内容进行介绍。

首先是FE 00,其中FE(254)对应RDB_OPCODE_SELECTDB常量是查询数据库的标志,00即代表 0 号数据库。

然后是FB 01 00,其中FB(251)对应RDB_OPCODE_RESIZEDB常量是查询该数据库大小的标志,根据if ((res = rdbSaveLen(rdb,db_size)) < 0) goto werr;知道01代表数据库的大小,即只有一条数据,根据if ((res = rdbSaveLen(rdb,expires_size)) < 0) goto werr;知道00代表没有包含过期标志的数据。

最后是00 05 68 65 6C 6C 6F 05 77 6F 72 6C 64,代表具体的数据内容。其中开始的00代表类型是字符串,参考源码可知(RDB_TYPE_STRING 的值是 0):

/* Save the object type of object "o". */
int rdbSaveObjectType(rio *rdb, robj *o) {
    switch (o->type) {
    case OBJ_STRING:
        return rdbSaveType(rdb,RDB_TYPE_STRING);
    case OBJ_LIST:
        if (o->encoding == OBJ_ENCODING_QUICKLIST || o->encoding == OBJ_ENCODING_LISTPACK)
            return rdbSaveType(rdb, RDB_TYPE_LIST_QUICKLIST_2);
        else
            serverPanic("Unknown list encoding");
    case OBJ_SET:
        if (o->encoding == OBJ_ENCODING_INTSET)
            return rdbSaveType(rdb,RDB_TYPE_SET_INTSET);
        else if (o->encoding == OBJ_ENCODING_HT)
            return rdbSaveType(rdb,RDB_TYPE_SET);
        else if (o->encoding == OBJ_ENCODING_LISTPACK)
            return rdbSaveType(rdb,RDB_TYPE_SET_LISTPACK);
        else
            serverPanic("Unknown set encoding");
    case OBJ_ZSET:
        if (o->encoding == OBJ_ENCODING_LISTPACK)
            return rdbSaveType(rdb,RDB_TYPE_ZSET_LISTPACK);
        else if (o->encoding == OBJ_ENCODING_SKIPLIST)
            return rdbSaveType(rdb,RDB_TYPE_ZSET_2);
        else
            serverPanic("Unknown sorted set encoding");
    case OBJ_HASH:
        if (o->encoding == OBJ_ENCODING_LISTPACK)
            return rdbSaveType(rdb,RDB_TYPE_HASH_LISTPACK);
        else if (o->encoding == OBJ_ENCODING_HT)
            return rdbSaveType(rdb,RDB_TYPE_HASH);
        else
            serverPanic("Unknown hash encoding");
    case OBJ_STREAM:
        return rdbSaveType(rdb,RDB_TYPE_STREAM_LISTPACKS_3);
    case OBJ_MODULE:
        return rdbSaveType(rdb,RDB_TYPE_MODULE_2);
    default:
        serverPanic("Unknown object type");
    }
    return -1; /* avoid warning */
}

随后的05 68 65 6C 6C 6F中的 05表示键的长度是5,对应68 65 6C 6C 6Fhello。最后的05 77 6F 72 6C 64代表值的长度也是 5,内容是77 6F 72 6C 64world

0x03 尾部信息

FF 18 7F 33 2E 0F C6 20 19,根据源码#define RDB_OPCODE_EOF 255可知,FF(25)是文件的 EOF 即结束标志。随后的 8 位根据源码可知对应 CRC64 校验码:

/* EOF opcode */
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;

/* CRC64 checksum. It will be zero if checksum computation is disabled, the
 * loading code skips the check in this case. */
cksum = rdb->cksum;
memrev64ifbe(&cksum);
if (rioWrite(rdb,&cksum,8) == 0) goto werr;

AOF

AOF 用于对数据库的增量备份,如果需要开启,需要将配置文件中的appendonly设置为 yes。同时,根据需要可以,设置appenddirname对应保存的文件夹,设置appendfilename用于配置文件名,设置appendfsync 用于配置频率。开启后,可以在指定的文件夹下看到类似以下的文件结构:

image-20231117142130770

其中 rdb 结尾的代表是 AOF 备份的基准文件,aof 文件是增量备份的执行命令信息,manifest 文件是记录 aof 文件的元数据信息。

0x00 dump.aof.1.base.rdb

通过十六进制编辑器打开该文件,可以发现内容和 RDB 中的格式一致(创建数据前备份的,所以没有数据部分):

在这里插入图片描述

而由于是 AOF 的基准文件,这里aof-base的值是01即代表是基准文件。

0x01 dump.aof.1.incr.aof

文本文件,内容如下(*开头代表命令包含的参数个数,$开头代表命令的长度):

*2       // 两个参数
$6       // 第一个参数长度为 6, 对应 SELECT 的长度
SELECT   
$1       // 第二个参数长度为 1, 对应 0, 即 0 号数据库
0
*3       // 三个参数
$3       // 第一个参数长度为 3, 对应 set 的长度
set
$5       // 第二个参数长度为 5, 对应 hello 的长度
hello
$0       // 第三个参数长度为 0

*3       // 三个参数
$3       // 第一个参数长度为 3, 对应 set 的长度
set
$5       // 第二个参数长度为 5, 对应 hello 的长度
hello
$5
world    // 第三个参数长度为 5, 对应 world 的长度

0x02 dump.aof.manifest

文本文件,内容如下:

file dump.aof.1.base.rdb seq 1 type b
file dump.aof.1.incr.aof seq 1 type i

其中seq 1 代表文件序号为 1,type b代表type base即基准文件,type i代表type increment即增量文件。

总结

本文根据一个简单的 RDB 文件讲解了 RDB 文件的存储格式,同时也简单介绍了 AOF 的文件格式。关于 RDB 中的 LZF 压缩算法和更复杂数据的存储方式(包含过期时间,数据类型为 Set,Map)等未作介绍,将留到下次。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/170077.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

音视频同步笔记 - 以音频时间为基

音视频同步 - 以音频时间为基 上图介绍&#xff1a; 该图是以音频的时间为基&#xff0c;对视频播放时间的延迟控制方案&#xff0c;只调整视频的播放延时。delayTime是视频播放的延迟时间&#xff0c;初始值是1 / FPS * 1000 (ms)&#xff0c;如果FPS为25帧率&#xff0c;初始…

互联网上门洗衣洗鞋小程序搭建

“闪站侠互联网洗护软件开发”围绕健康洗护、智能操作做出不断升级&#xff0c; 满足用户多样化的洗护需求&#xff0c;打造轻松洗衣洗鞋体验。 洗衣洗鞋专用软件&#xff0c;可以帮助洗衣店洗鞋店店主们省心高效的管理店铺&#xff0c;一次付款长期使用&#xff0e;功能基本涵…

chatGPT PLUS 绑卡提示信用卡被拒的解决办法

一、 ChatGPT Plus介绍 作为人工智能领域的一项重要革新&#xff0c;ChatGPT Plus的上线引起了众多用户的关注&#xff0c;其背后的OpenAI表现出傲娇的态度&#xff0c;被誉为下一个GTP 4.0。总的来说&#xff0c;ChatGPT Plus的火爆主要有两个原因。首先&#xff0c;其在人工…

归并排序知识总结

归并排序思维导图&#xff1a; 知识点&#xff1a;如果原序列中两个数的值是相同的&#xff0c;它们在排完序后&#xff0c;它们的位置不发生变化&#xff0c;那么这个排序是稳定的。快速排序是不稳定的&#xff0c;归并排序是稳定的。 快排变成稳定的>使快排排序数组中的每…

11.20顺序表查找,质数查找,折半查找,任意折查找

概念 顺序表查找 int search(int *a,int n, int key){ int i; a[0]key; in; while(a[i]!key){ i--;} return i;} 就是从数组a的尾部开始找&#xff0c;a是从1开始计数的&#xff0c;所以找到0时&#xff0c;就说明查找失败。 顺序表找最大值 mva[1]; for(int i2;i<n;i){…

基于 Python中的深度学习:神经网络与卷积神经网络

当下&#xff0c;深度学习已经成为人工智能研究和应用领域的关键技术之一。作为一个开源的高级编程语言&#xff0c;Python提供了丰富的工具和库&#xff0c;为深度学习的研究和开发提供了便利。本文将深入探究Python中的深度学习&#xff0c;重点聚焦于神经网络与卷积神经网络…

实现领域驱动设计-应用结构

写在前面&#xff1a; DDD的一大好处便是它并不需要使用特定的架构。我们可以在整个系统中使用多种风格的架构。有些架构包围着领域模型&#xff0c;能够全局性地影响系统&#xff0c;而有些架构则满足了某些特定的需求。我们的目标是选择适合于自己的架构和架构模式。 在选择架…

Java格式化类Format

文章目录 Format介绍Format方法- format&#xff08;格式化&#xff09;- parseObject&#xff08;解析&#xff09; 格式化分类日期时间格式化1. DateFormat常用方法getInstancegetDateInstancegetTimeInstancegetDateTimeInstance 方法入参styleLocale 2. SimpleDateFormat常…

【HarmonyOS】鸿蒙应用开发基础认证题目

系列文章目录 【HarmonyOS】鸿蒙应用开发基础认证题目&#xff1b; 文章目录 系列文章目录前言一、判断题二、单选题三、多选题总结 前言 随着鸿蒙系统的不断发展&#xff0c;前不久&#xff0c;华为宣布了重磅消息&#xff0c;HarmonyOS next 开发者版本会在明年&#xff08;…

编写函数实现简单的插值进入有序数组问题

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 老骥伏枥&#xff0c;志在千里&…

JVS低代码表单设计:数据联动详解(多级数据级、数据回显等)

在这信息化时代&#xff0c;表单作为数据的收集和展示工具&#xff0c;已经渗透到不同的角落。JVS低代码对表单的设计和操作进行了不断的优化和创新。其中&#xff0c;联动回显作为一项重要的功能&#xff0c;无论是多级数据级联控制、组件的联动控制&#xff0c;还是多表的数据…

Unexpected WSL error

问题描述 启动 Docker Desktop 报错 Unexpected WSL error&#xff0c;报错完整信息如下&#xff1a; Docker Desktop - Unexpected WSL error An unexpected error was encountered while executing a WSL command, Commoncauses include access rights issues, which occur…

[github初学者教程] 分支管理-以及问题解决

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于新西兰奥克兰大学攻读IT硕士学位。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。跨领域…

虚拟化逻辑架构: 创建KVM中的VM与实现VNC远程登录

目录 一、实验 1.安装KVM环境管理工具并创建VM&#xff08;虚拟机&#xff09; 2.Windows使用VNC Viewer连接KVM中的VM&#xff08;虚拟机&#xff09; 二、问题 1.如何下载安装VNC Viewer 一、实验 1.安装KVM环境管理工具并创建VM&#xff08;虚拟机&#xff09; (1) 采…

golang学习笔记——日志记录

文章目录 日志与错误log包记录到文件记录框架Contextual LoggingLeveled LoggingSetting Global Log Level Error Logging 日志与错误 通常&#xff0c;发生错误时&#xff0c;最终用户只会看到一条消息&#xff0c;指示程序出现问题。日志是简单错误消息以外的更多信息。 lo…

城市智慧路灯智能照明管理系统简介

城市路灯存在着开关灯控制方式单、亮灯时间不准确、巡查困难、故障处理不及时、亮灯率无法把控等问题&#xff0c;从而导致路灯系统能耗高&#xff0c;维护成本高。传统的路灯控制系统已无法满足智慧城市管理的需要&#xff0c;智能路灯照明控制系统从而得到广泛应用。 叁仟智…

【C++】使用std::vector()函数实现矩阵的加、减、点乘、点除等运算

本文通过vector&#xff08;&#xff09;函数表示矩阵的形式&#xff0c;对 加、减、点乘、点除等运算进行编码和运行&#xff0c;相应结果如下文所述。 #include <iostream> #include <vector>using namespace std;// 矩阵加法 vector<vector<int>> …

buildadmin+tp8表格操作(4) Table组件,baTable类和 elementplus中的属性关系

在buildadmin 中&#xff0c;table组件是封装的 element-plus中的方法&#xff0c; 所以说&#xff0c; 在 buildadmin的table组件中&#xff0c;是可以通用 elementplus中的属性的 以上这些属性&#xff0c; 在buildadmin中都是可以使用的 使用方式和 elementplus el-table用…

linux rsyslog综合实战1

本次我们通过rsyslog服务将A节点服务器上的单个日志(Path:/var/log/245-1.log)实时同步到B节点服务器目录下(Path:/opt/rsyslog/245) 1.rsyslog架构 2.环境信息 环境信息 HostnameIpAddressOS versionModuleNotersyslog1192.168.10.245CentOS Linux release 7.9.2009 (Core)rs…

工作记录---为什么双11当天不能申请退款?(有趣~)

为什么&#xff1f; 服务降级了 服务降级&#xff1a; 当服务器压力剧增的情况下&#xff0c;根据实际业务情况及流量&#xff0c;对一些服务和页面有策略的不处理或换种简单的方式处理&#xff0c;从而释放服务器资源以保证核心交易正常运作或高效运作。 分布式系统的降级…
最新文章