Redis Source Code Read Log( 12 robj & sds)

Part 1: redisObject robj

definitions

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

type

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

如上图,当数据为 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 个字节长度的连续内存。

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内存)

Part 2: sds

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

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

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

definitions

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语言编译器预处理时双 ”#” 的粘连特性。

内存: (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 域的类型,往前移动对应长度的距离后,取整个长度的头指针,类型强转之后,直接成员访问方式取用即可。

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 的字符串,则会采用该种数据类型进行存储。

sdshdr8:

1. type: SDS_TYPE_8

2. hdr 长度 3

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

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

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

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

sdshdr16:

1. type: SDS_TYPE_16

2. hdr 长度 5

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

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

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

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

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 语义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值