Redis Source Code Read Log( 12 robj & sds)

Part 1: redisObject robj

definitions

Redis Source Code Read Log( 12 robj & sds)

Redis Source Code Read Log( 12 robj & sds)

Redis Source Code Read Log( 12 robj & sds)

Redis Source Code Read Log( 12 robj & sds)

针对原字节流,Redis内部根据不同场景,结合 sds 进行不同的 object 构造。而 object 模块就是“工厂模式”下,二进制字节流的消费者,以及 Redis server 中对象 redisObject(robj) 的生产者

type

robj 内部元素的类型。这个类型的定义就是实际的 redis 的数据类型。如下所示。Redis 支持的数据类型,实际还另外包含一些基本类型。如 dictEntry 中定义的:

Redis Source Code Read Log( 12 robj & sds)

Redis Source Code Read Log( 12 robj & sds)

如上图,当数据为 u64,s64,double等,可能会采用基础的数据类型,否则采用 val 域指针,指向一个实际的 robj

encoding

encoding 是针对不同 typeRedis 内部进行不同的数据进行编码组织。比如前文所述中的,hash 类型,内部编码含两种,ziplist 以及 hash

type hash , encoding 可能的取值如下:

OBJ_ENCODING_HT

OBJ_ENCODING_ZIPLIST

typeset , encoding 可能的取值如下:

OBJ_ENCODING_INTSET

OBJ_ENCODING_HT

type OBJ_STRING , encoding 可能的取值如下:

OBJ_ENCODING_RAW

OBJ_ENCODING_EMBSTR

refcount

refcount 是守护域。 对 redisObject 进行守护,自动管理一个 robj 自身以及内部 ptr 的内存,如果是堆区内存,会进行相应释放操作,包含针对不同 type 以及不同 encoding ptr 对象进行 dcor 的操作。

lru

lru 是呼应的是 redis 内部的 maxmemory policy 策略 域。redis 内部有另外一个模块,专门处理这个策略,进行 rediskey 值的淘汰: evcit 模块。

淘汰主要是两个策略:

1. LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。

2. LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。

共计 24 bit

LFU 下:

8 bit:  使用率,就是访问次数

16 bit: touch 访问的时间,单位是 time() 返回值/60 得到的分钟数

LRU:

gettimeofday 返回值 换算成 秒。

ptr

robj 中的实际类型,结合 type + encoding,便可提取其精确值。

redis 中的 string object

type: OBJ_STRING

1. encoding: OBJ_ENCODING_EMBSTR

当 所需创建的 string 长度 低于 44() 个字节,那么将采用这种编码方式进行存储,实际内部存储的是 sdshdr8 类型的数据,并且,此时整个的 robj 包含内部所携带的 sds 对象 都是 连续内存。

比如,长度为 5 个字符的”hello”robj的总长度以及内存布局如下:

当存储 5 个字节长度”hello”字符串,封装成 robj 25 个字节长度的连续内存。

Redis Source Code Read Log( 12 robj & sds)

2. encoding: OBJ_ENCODING_RAW

分档:

0. 44B,已经被排除

1. < 256 B, 不带 = 号,因为 需要 + \0 结束符,不能超标。此时采用 sdshdr8 类型

2. 256 B <= target < 65536 B (64 KB), 采用 sdshdr16 类型 (这个已经足矣)

3. 65536B <= target < (1 << 32) = 4GB, 采用 sdshdr32 类型(最多可达4GB内存)

Redis Source Code Read Log( 12 robj & sds)

Part 2: sds

sds Redis 内部对 C-style 风格字符串进行内存管理的库。内部提供了一套较为友好的 C-API 便于开发人员使用。

鉴于 Redis 内部多数情况下都是对字符串的操作。

无论是 key 值, string 类型的 value 值, hash 数据的 field 以及 field 对应的 value 等等。都是 string 类型。 而且针对各类 value 值,其都是动态变化的。

definitions

Redis Source Code Read Log( 12 robj & sds)

Redis Source Code Read Log( 12 robj & sds)

1. __attribute__ ((__packed__))  表示 去除 C 语言 struct 定义中的对齐以及补齐操作。

2. len: 表示当前实际使用的数量

3. alloc: 实际申请的数量

4. flags: 类型,标志该结构的体的类型,只使用低位3bit。类型值如右上图所示

5. buf[]: C语言中结构体的特殊用法,实现中,不占用内存,sizeof 计算该结构体长度

申请实际返回的是 buf 指针,头指针,采用 C 语言的特殊语法:

sdshdr64 * hdr = (sdshdr64*)buf[0 – sizeof(sdshdr64)];

hdr_len = sizeof(sdshdr64);

static inline void FunctionDefinition() {}:

头文件中实现函数定义,规避 multiple definition的做法,并且申请内联,一定程度上优化代码执行效率。

另外,sds 模块还是用了C语言编译器预处理时双 ”#” 的粘连特性。

Redis Source Code Read Log( 12 robj & sds)

内存: (Unix环境高级编程)

ISO C 的三种方式

malloc: 分配指定自己的存储区。内部初始值不确定。参数为需要申请的内存大小 ,返回新申请内存的首地址 void *。如果出错,如OOM,返回NULL

calloc: 为指定长度的对象分配存储空间。申请的空间,每一个 bit 都会被置 0.

realloc: 增加或减少以前分配去的长度。当增加长度时,可能需要将以前分配区的内容移动到一个足够大的区域,以便在尾端提供增加的区域,而新增区域的初始值则不确定。

其中 realloc 以及 free 中的 ptr, 必须是 malloc, calloc, realloc 的返回值。

C++ 中:

void * p = (ClassObject*)malloc(sizeof(ClassObject));

ClassObject * inst = new (p) ClassObject;  // 原址构造

注意 free(p);

jemalloc Google 提供的 TCMalloc

SDS 提供的类型,

type 类型

针对不同的类型,在SDS 内部都会额外的申请一部分的结构体头,记录了一些有用的信息。

主要有

1. flags类型域,固定占用一个字节,取用的方式是  sds 的对象 s, 取前一个字节  s[-1].

2. len : 实际的存储的字符串的长度。不包含 \0 终止符。

3. alloc : 实际申请的总长度,这个总长度,不包含 1. 尾部 \0, 2. 头 实际就是 sizeof(buf) - 1.

len alloc 的获取方式,与 flags 不同。

flags 域是可以直接取用的。但是 len alloc 是不可以的。

需要 根据 flags 域的类型,往前移动对应长度的距离后,取整个长度的头指针,类型强转之后,直接成员访问方式取用即可。

Redis Source Code Read Log( 12 robj & sds)

sdshdr5:

1. type: SDS_TYPE_5

2. hdr 长度 1

3. 能够hold的数据最长长度 32 (25次方)

4. len alloc

5. 长度 len 存储在 flags 5 bit, 低 3 bit 存储的是类型。

6. 由于无 alloc 域,去 available的时候,直接返回 0.

当用户层直接采用 长度小于 32,并且大于 0 的字符串,则会采用该种数据类型进行存储。

Redis Source Code Read Log( 12 robj & sds)

sdshdr8:

1. type: SDS_TYPE_8

2. hdr 长度 3

3. 能够hold的数据最长长度 256 (28次方)

其总长度  是  hdr 长度 + alloc 长度 + \0 终止符

当需要存储的数据长度 大于 32 小于 256,系统默认采用这种数据结构进行存储。

首次使用的时候,len alloc 都采用的是实际有效数据的长度。

Redis Source Code Read Log( 12 robj & sds)

sdshdr16:

1. type: SDS_TYPE_16

2. hdr 长度 5

3. 能够hold的数据最长长度 65536 (216次方) 64K

其总长度  是  hdr 长度 + alloc 长度 + \0 终止符

当需要存储的数据长度 大于 256 小于 65536 (64K),系统默认采用这种数据结构进行存储。

首次使用的时候,len alloc 都采用的是实际有效数据的长度。

Redis Source Code Read Log( 12 robj & sds)

sdshdr32:

1. type: SDS_TYPE_32

2. hdr 长度 9

3. 能够hold的数据最长长度 4G (232次方)

其总长度  是  hdr 长度 + alloc 长度 + \0 终止符

当需要存储的数据长度 大于 65536 小于 4G,系统默认采用这种数据结构进行存储。

首次使用的时候,len alloc 都采用的是实际有效数据的长度。

sds 提供的功能汇总

1. SDS 自身提供了 len 域,提供了对内部持有数据长度进行 O(1) 级别的访问

2. SDS 的访问,第一尾部保持了一个 \0,第二len与记录长度,所以相比原始 C-style 字符串,sds安全性更高

3. 延迟释放:由于持有 len 以及 alloc,不立即释放内存,而是直接减小len

4. 扩容机制:

当内部 available 的内存够用,则无需释放原有内存,申请新内存

当内部 available 不够用,首先查看 hdr 类型,如果不变,则 realloc,否则才会free原有内存,然后malloc新内存

5. 另外,redis 内部对内存库在编译期间有选项控制,用户可根据需要进行选择(Jemalloc,TCMalloc)

redis sds 进行了面向对象式的封装,其功能有了一定程度的提升,但是,相比标准 C++ std::string 类型仍旧略显逊色(个人感觉),尤其相比 C++ move 语义。