时间:2021-07-01 10:21:17 帮助过:9人阅读
举例,对string“hello world”编码结果如下:
0xC “hello, world”
0xC以1个字节表示字符串长度为12字节,后续12字节即为字符串的真实值。由于是非前缀码,因此根据第1个字节的值完全可以进行区分是哪种格式,反序列化时首先读1个字节的内容,然后根据该字节的值即可选择正确的操作。
redis中rdb.c源文件中的rdbSaveLen函数完成长度的编码,而rdbLoadLen完成长度值的解码。
当需要序列化的值为整形值时,整形值会以二进制的形式直接进行序列化,而不是转换成ascii码的字符串形式进行序列化,从而进一步节约空间。此外,根据整形值的大小,redis同样以尽可能少的空间来存储该值。与字符串类似,首先有一个字节的高2bits设置为11代表后续的值是一个整形值,同时以低6bits的值区分使用的字节数。
整体形式如下:
11000000[8 bit integer] 11000001[16 bit integer] 11000002[32 bit integer]
举例,序列化整形值0x300的结果如下:
0xC1 0x0300
0xC1表示后续是一个以int16形式存储的整形值,0x0300即为该整形值。由于第1个字节的高2bits为11,与string类型表示长度的格式不相同,所此可以区分是string还是int型。
此外,若第1个字节的高2bits为11,低6bits为3(RDB_ENC_LZF),那么后续的数据为压缩数据,该字节后跟着两个长度值,分别表示压缩后的数据长度与压缩前的数据长度,再后面才是真正的数据。
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个字节的字符串值。
反序列化时:
举例,一个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个成员依次以整形的格式序列化。
反序列化时:
除了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_*值,它表示了后续数据的存储方式,从而反序列化时采取正确的操作。
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的序列化调用关系。
1.读取9字节magic标志,并验证
循环进行2-3步
2. 读取1字节RDB_TYPE_*或者RDB_OPCODE_*标志
3. 根据RDB_OPCODE_*或者RDB_TYPE_*的值做相应的处理
上述第3步中,如果需要读取string或者int这种基本类型,处理过程为:
如果对应的类型为复合类型,如list、set等,处理过程为:
反序列化的输入为文件,即使序列化的输出目的地为sockets,接收端也会先将数据存储到一个文件中,然后再从文件反序列化。反序列化的调用栈为
由于redis是单线程模式,因此它选择将持久化操作放在子进程中进行,否则持久化过程中将停止响应请求。
根据fork函数的特性,子进程创建后与父进程拥有相同的内存内容,因此fork函数调用后子进程即得到了此时db中的完整内容。并且由于copy-on-write特性,并不会发生大量的内存copy,仅在write操作发生时,相应的内存页才进行一个copy生成副本,即该操作也不会特别耗时。
但相应的,父进程继续接受客户端的命令,修改的内容并不会反应到子进程的内存中,因此rdb持久化过程中出现的修改将会丢失。
redis源码分析(三)--rdb持久化
标签:设置 基本类型 根据 字符 64 bit 线程 处理 函数调用 mem