Redis rdb持久化

Redis支持两种持久化方式:rdb与aof。rdb将一个节点上的内存数据序列化后存储到磁盘中,序列化的数据以尽可能节约空间的方式存储,并非完全的ascii表示。它的优点在于节约空间,恢复速度快,缺点在于每一次操作都需要对整个内存数据进行序列化,并且持久化过程中的修改被丢失。而aof将数据以操作命令的方式进行存储,从aof恢复数据即从aof文件读入命令再执行命令。它的优点是可以记录持久化过程中的产生的命令,而缺点在于完全以ascii编码,使用空间更多,且恢复速度更慢。这篇文件章主要对rdb的持久化方式进行大致介绍。

Redis中有多种数据类型,如string、set、zset、hash、list等,并且具有多个db(一个db即是一个字典,存储了string、list、set等类型的数据),为了完成不同类型数据的序列化,Redis设计了相应的持久化格式。

1. 序列化string

序列化string时,首先会存储string的长度,然后存储string的真实值。为了节约空间,对于不同长度的string,redis使用尽可能少的空间来存储它的长度,因此设计了如下的字符串长度存储格式:

  1. len < 1<<6,使用1个字节编码长度,该字节的高2bits为00,低6bits代表长度
  2. 1<<6 <= len < 1<<14,使用2个字节编码长度,第1个字节的高两bits为01,后续14bits代表长度
  3. 1<<14 <= len <= UINT32_MAX,使用5个字节编码长度,第1个字节为0x80,后续4字节代表长度
  4. UINT32_MAX < len,使用9个字节编码长度,第1个字节为0x81,后续8字节代表长度

Redis中相应的定义如下:

/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
* the first byte to interpreter the length:
*
* 00|XXXXXX => if the two MSB are 00 the len is the 6 bits of this byte
* 01|XXXXXX XXXXXXXX => 01, the len is 14 byes, 6 bits + 8 bits of next byte
* 10|000000 [32 bit integer] => A full 32 bit len in net byte order will follow
* 10|000001 [64 bit integer] => A full 64 bit len in net byte order will follow
* 11|OBKIND this means: specially encoded object will follow. The six bits
* number specify the kind of object that follows.
* See the RDB_ENC_* defines.
*
* Lengths up to 63 are stored using a single byte, most DB keys, and may
* values, will fit inside. */

举例,对string“hello world”编码结果如下:

0xC “hello, world”

0xC以1个字节表示字符串长度为12字节,后续12字节即为字符串的真实值。由于是非前缀码,因此根据第1个字节的值完全可以进行区分是哪种格式,反序列化时首先读1个字节的内容,然后根据该字节的值即可选择正确的操作。

redis中rdb.c源文件中的rdbSaveLen函数完成长度的编码,而rdbLoadLen完成长度值的解码。

2. 序列化int

当需要序列化的值为整形值时,整形值会以二进制的形式直接进行序列化,而不是转换成ascii码的字符串形式进行序列化,从而进一步节约空间。此外,根据整形值的大小,redis同样以尽可能少的空间来存储该值。与字符串类似,首先有一个字节的高2bits设置为11代表后续的值是一个整形值,同时以低6bits的值区分使用的字节数。

  1. (value >= -(1<<7) && value <= (1<<7)-1),低6bit值为0,后续1个字节存储整形值。
  2. (value >= -(1<<15) && value <= (1<<15)-1),低6btis值为1,后续2个字节存储整形值。
  3. (value >= -((long long)1<<31) && value <= ((long long)1<<31)-1),低6bits值为2,后续4个字节存储整形值。

整体形式如下:

[ bit integer]
[ bit integer]
[ bit integer]

举例,序列化整形值0x300的结果如下:

0xC1 0x0300

0xC1表示后续是一个以int16形式存储的整形值,0x0300即为该整形值。由于第1个字节的高2bits为11,与string类型表示长度的格式不相同,所此可以区分是string还是int型。

此外,若第1个字节的高2bits为11,低6bits为3(RDB_ENC_LZF),那么后续的数据为压缩数据,该字节后跟着两个长度值,分别表示压缩后的数据长度与压缩前的数据长度,再后面才是真正的数据。

3. RDB_TYPE_*与RDB_OPCODE_*

Redis是一个key-value缓存系统,所有的值都以key-value的形式存储在db字典中。但是redis中value的类型并不仅限于string,它还可以是结构体类型,如set、list、hash等。为了序列化这些类型,redis中首先会以一个字节存储数据类型,然后如果是复合类型,会存储该类型的成员数目,然后遍历成员值并以基本的int或者string类型进行存储。

Redis中定义了如下的类型值:

/* Map object types to RDB object types. Macros starting with OBJ_ are for
* memory storage and may change. Instead RDB types must be fixed because
* we store them on disk. */
#define RDB_TYPE_STRING 0
#define RDB_TYPE_LIST 1
#define RDB_TYPE_SET 2
#define RDB_TYPE_ZSET 3
#define RDB_TYPE_HASH 4
#define RDB_TYPE_ZSET_2 5 /* ZSET version 2 with doubles stored in binary. */
#define RDB_TYPE_MODULE 6
#define RDB_TYPE_MODULE_2 7 /* Module value with annotations for parsing without
the generating module being loaded. */
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */ /* Object types for encoded objects. */
#define RDB_TYPE_HASH_ZIPMAP 9
#define RDB_TYPE_LIST_ZIPLIST 10
#define RDB_TYPE_SET_INTSET 11
#define RDB_TYPE_ZSET_ZIPLIST 12
#define RDB_TYPE_HASH_ZIPLIST 13
#define RDB_TYPE_LIST_QUICKLIST 14
#define RDB_TYPE_STREAM_LISTPACKS 15
/* NOTE: WHEN ADDING NEW RDB TYPE, UPDATE rdbIsObjectType() BELOW */

这些类型值代表了被序列化的value的类型,如list、set等,此外还代表了value的具体实现方式,如set可以使用hash表实现,相应类型值为RDB_TYPE_SET,也可以使用一个有序的整形数组实现,对应类型值为RDB_TYPE_SET_INTSET。反序列化时,首先读取1个字节的值判断后续数据的类型,然后进行相应的类型重建。

举例,一个string类型的key-value对 “key1” “hello, world”的序列化结果为:

RDB_TYPE_STRING 0x4 “key1” 0xC “hello, world”

首先1个字节的类型值RDB_TYPE_STRING,然后一个字节的长度值0x4,即后面有4个字节的关键字字符串,然后一个字节的长度值0xC,即后面有12个字节的字符串值。

反序列化时:

  1. 首先读取1个字节得RDB_TYPE_STRING,得到后续为一个string对象
  2. 然后读取长度值0x4,并读取相应的4个字节得到关键字,
  3. 最后再读取长度0xC,并读取相应的12字节值得到value值。

举例,一个list类型的key-value对:”key2” 2 3 4 0x300的序列化结果为:

RDB_TYPE_LIST 0x4 “key2” 0x4 0xc0 0x2 0xc0 0x3 0xc0 0x4 0xC1 0x0300

首先是1个字节的类型值RDB_TYPE_LIST,然后长度值为0x4,表示有4个字节的关键字字符串,然后是0x4表示该list有4个成员,然后4个成员依次以整形的格式序列化。

反序列化时:

  1. 首先读取1个字节得到RDB_TYPE_LIST,得到后续为一个list对象,
  2. 读取关键字的长度为0x4,读取4字节得到”key2”
  3. 读取长度0x4,得到list成员数目为4
  4. 读取长度,然后读取整形值,循环4次,完成list对象的重建。

除了RDB_TYPE*与redis中存储的数据类型对应,还有一类RDB_OPCODE*表示一些其它的数据,如:RDB_OPCODE_EXPIRETIME表示后面的数据是该对象的超时时间;RDB_OPCODE_SELECTDB表示接下来的数据是一个db索引,直到遇到下一个RDB_OPCODE_SELECTDB之前,所有反序列化的数据都应该存储在该索引的db字典中。Redis中定义了如下类型的RDB_OPCODE*值:

/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
#define RDB_OPCODE_IDLE 248 /* LRU idle time. */
#define RDB_OPCODE_FREQ 249 /* LFU frequency. */
#define RDB_OPCODE_AUX 250 /* RDB aux field. */
#define RDB_OPCODE_RESIZEDB 251 /* Hash table resize hint. */
#define RDB_OPCODE_EXPIRETIME_MS 252 /* Expire time in milliseconds. */
#define RDB_OPCODE_EXPIRETIME 253 /* Old expire time in seconds. */
#define RDB_OPCODE_SELECTDB 254 /* DB number of the following keys. */
#define RDB_OPCODE_EOF 255 /* End of the RDB file. */

这些特殊类型后续值的长度通常是固定的,如RDB_OPCODE_EXPIRETIME以32bit表示超时时间,单位为s;RDB_OPCODE_EXPIRETIME_MS后面是64bit表示的超时时间,单位为ms;RDB_OPCODE_SELECTDB后续是使用的db索引,以len的编码方式进行存储,而RDB_OPCODE_AUX表示一些key-value对,而这些key, value都是string与int等基本类型。

除了rdb文件开头的固定字节的magic码,所有rdb序列化的数据都有一个前置的RDB_TYPE_*值或者RDB_OPCODE_*值,它表示了后续数据的存储方式,从而反序列化时采取正确的操作。

4. RDB序列化流程

1. 序列化前置信息,如magic识别码,版本号,时间戳

2.遍历db,序列化每一个db

2.1 序列化RDB_OPCODE_SELETCTDB

2.2 序列化RDB_OPCODE_RESIZEDB,存储该db的大小

2.3 序列化db中的每一个key-value对

2.3.1 序列化超时时间RDB_OPCODE_EXPIRETIME

2.3.2 序列化lru值,RDB_OPCODE_IDLE

2.3.3 序列化LFU值,RDB_OPCODE_FREQ

2.3.4 序列化值类型

2.3.5 序列化key(string)

2.3.6 序列化value

在前一篇文件中介绍过redis的rio抽象层,而这些序列化操作正是以rio作为接口,以rio为目的地,既可以将序列化内容输出到文件,也可以将序列化内容输出到多个sockets中。普通的持久化操作使用文件作为输出对象,而在master-slave中的数据同步可能会使用到sockets作为输出对象,通过rio的抽象,将序列化与底层io进行解藕。

redis中的序列化函数调用栈如下:

左图是输出对象为文件的序列化调用关系,右图是输出对象为sockets的序列化调用关系。

5. RDB反序列化流程

1.读取9字节magic标志,并验证

循环进行2-3步

2. 读取1字节RDB_TYPE_*或者RDB_OPCODE_*标志

3. 根据RDB_OPCODE_*或者RDB_TYPE_*的值做相应的处理

上述第3步中,如果需要读取string或者int这种基本类型,处理过程为:

  1. 调用rdbLoadLen读取长度
  2. 根据rdbLoadLen返回值读取string或者整形值

如果对应的类型为复合类型,如list、set等,处理过程为:

  1. 调用rdbLoadLen读取复合类型成员数目
  2. 循环读取成员值,直到指定数目的值被读出。读取成员的操作即读取string或者int基本类型。

反序列化的输入为文件,即使序列化的输出目的地为sockets,接收端也会先将数据存储到一个文件中,然后再从文件反序列化。反序列化的调用栈为

  1. rdbLoad,初始化rio为文件流
  2. rdbLoadRio以rio为输入,从文件中读取数据并完成反序列化。

6. 后台进程

由于redis是单线程模式,因此它选择将持久化操作放在子进程中进行,否则持久化过程中将停止响应请求。

根据fork函数的特性,子进程创建后与父进程拥有相同的内存内容,因此fork函数调用后子进程即得到了此时db中的完整内容。并且由于copy-on-write特性,并不会发生大量的内存copy,仅在write操作发生时,相应的内存页才进行一个copy生成副本,即该操作也不会特别耗时。

但相应的,父进程继续接受客户端的命令,修改的内容并不会反应到子进程的内存中,因此rdb持久化过程中出现的修改将会丢失。

redis源码分析(三)--rdb持久化的更多相关文章

  1. redis源码分析(四)--aof持久化

    Redis aof持久化 Redis支持两种持久化方式:rdb与aof,上一篇文章中已经大致介绍了rdb的持久化实现,这篇文章主要介绍aof实现. 与rdb方式相比,aof会使用更多的存储空间,因为它 ...

  2. Redis源码分析:serverCron - redis源码笔记

    [redis源码分析]http://blog.csdn.net/column/details/redis-source.html   Redis源代码重要目录 dict.c:也是很重要的两个文件,主要 ...

  3. redis源码分析之事务Transaction(下)

    接着上一篇,这篇文章分析一下redis事务操作中multi,exec,discard三个核心命令. 原文地址:http://www.jianshu.com/p/e22615586595 看本篇文章前需 ...

  4. tomcat源码分析(三)一次http请求的旅行-从Socket说起

    p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...

  5. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  6. redis源码分析之发布订阅(pub/sub)

    redis算是缓存界的老大哥了,最近做的事情对redis依赖较多,使用了里面的发布订阅功能,事务功能以及SortedSet等数据结构,后面准备好好学习总结一下redis的一些知识点. 原文地址:htt ...

  7. redis源码分析之事务Transaction(上)

    这周学习了一下redis事务功能的实现原理,本来是想用一篇文章进行总结的,写完以后发现这块内容比较多,而且多个命令之间又互相依赖,放在一篇文章里一方面篇幅会比较大,另一方面文章组织结构会比较乱,不容易 ...

  8. redis源码分析之有序集SortedSet

    有序集SortedSet算是redis中一个很有特色的数据结构,通过这篇文章来总结一下这块知识点. 原文地址:http://www.jianshu.com/p/75ca5a359f9f 一.有序集So ...

  9. Redis源码分析(intset)

    源码版本:4.0.1 源码位置: intset.h:数据结构的定义 intset.c:创建.增删等操作实现 1. 整数集合简介 intset是Redis内存数据结构之一,和之前的 sds. skipl ...

随机推荐

  1. BZOJ2151/洛谷P1792 题解

    若想要深入学习反悔贪心,传送门. Description: 有 \(n\) 个位置,每个位置有一个价值.有 \(m\) 个树苗,将这些树苗种在这些位置上,相邻位置不能都种.求可以得到的最大值或无解信息 ...

  2. 复旦高等代数I(19级)每周一题

    本学期的高等代数每周一题活动计划从第2教学周开始,到第15教学周结束,每周的周末公布一道思考题(共14道,思考题一般与下周授课内容密切相关),供大家思考和解答.每周一题将通过“高等代数官方博客”(以博 ...

  3. DNN的BP算法Python简单实现

    BP算法是神经网络的基础,也是最重要的部分.由于误差反向传播的过程中,可能会出现梯度消失或者爆炸,所以需要调整损失函数.在LSTM中,通过sigmoid来实现三个门来解决记忆问题,用tensorflo ...

  4. python 一个二维数组和一个整数,判断数组中是否含有该整数

    在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序. 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. de ...

  5. 回滚事件只是让原数据看起来不变,但是id还是会自增吗?

    回滚事件只是让原数据看起来不变,但是id还是会自增对吗? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ...

  6. [Web] 取消Promise

    转载自 为Promise插上可取消的翅膀 const makeCancelable = (promise) => { let hasCanceled_ = false; const wrappe ...

  7. android x86 固件定制

    测试提了几个bug 1.系统语言默认设置成中文,否则时间控件显示的内容有问题 2.关闭10分钟不操作自动休眠功能 3.默认关闭虚拟键盘,目的在文本控件点击后,虚拟键盘就会在右下角显示出来,导致物理键盘 ...

  8. Python快速入门教程【转】

    第一章 Python基础知识 1.1 介绍      1.1.1 特点      Python是一种面向对象.解释型计算机程序设计语言.语法简洁清晰,强制用空白符作为语句缩进.      Python ...

  9. docker 镜像制作(jupyter)

    docker pull centosdocker run -it -d --name test-centos1 centosdocker exec -it test-centos1 /bin/bash ...

  10. 批量kill掉包含某个关键字的进程

    需要把 linux 下符合某一项条件的所有进程 kill 掉,又不能用 killall 直接杀掉某一进程名称包含的所有运行中进程(我们可能只需要杀掉其中的某一类或运行指定参数命令的进程),这个时候我们 ...