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 是针对不同 type,Redis 内部进行不同的数据进行编码组织。比如前文所述中的,hash 类型,内部编码含两种,ziplist 以及 hash。
当 type 为 hash 时, encoding 可能的取值如下:
OBJ_ENCODING_HT
OBJ_ENCODING_ZIPLIST
当 type 为 set 时, 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 内部有另外一个模块,专门处理这个策略,进行 redis 中 key 值的淘汰: 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: 类型,标志该结构的体的类型,只使用低位3个bit。类型值如右上图所示
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 (2的5次方)
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 (2的8次方)
其总长度 是 hdr 长度 + alloc 长度 + \0 终止符
当需要存储的数据长度 大于 32 小于 256,系统默认采用这种数据结构进行存储。
首次使用的时候,len 与 alloc 都采用的是实际有效数据的长度。
sdshdr16:
1. type: SDS_TYPE_16
2. hdr 长度 5
3. 能够hold的数据最长长度 65536 (2的16次方) 即 64K
其总长度 是 hdr 长度 + alloc 长度 + \0 终止符
当需要存储的数据长度 大于 256 小于 65536 (64K),系统默认采用这种数据结构进行存储。
首次使用的时候,len 与 alloc 都采用的是实际有效数据的长度。
sdshdr32:
1. type: SDS_TYPE_32
2. hdr 长度 9
3. 能够hold的数据最长长度 4G (2的32次方)
其总长度 是 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 语义。