基于redis5.0的版本。
字符串对象的编码有:int,raw或者embstr。
1.raw
raw就是redisObject+sds
,即redisObject
的ptr
指针指向一个sds
对象。
// object.c
define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}
// rwa
robj *createRawStringObject(const char *ptr, size_t len) {
return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}
// embstr
/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
* an object where the sds string is actually an unmodifiable string
* allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1); // 申请连续的空间
struct sdshdr8 *sh = (void*)(o+1);
o->type = OBJ_STRING;
o->encoding = OBJ_ENCODING_EMBSTR;
o->ptr = sh+1;
o->refcount = 1;
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
} else {
o->lru = LRU_CLOCK();
}
sh->len = len;
sh->alloc = len;
sh->flags = SDS_TYPE_8;
if (ptr == SDS_NOINIT)
sh->buf[len] = '\0';
else if (ptr) {
memcpy(sh->buf,ptr,len);
sh->buf[len] = '\0';
} else {
memset(sh->buf,0,len+1);
}
return o;
}
2. embstr
如果字符串对象保存的是一个字符串值,并且这个字符粗值的长度小于等于44
字节(44这个值并不会一直保持不变,例如redis3.2版本之前是39),则使用embstr
编码,embstr即embedded string,“嵌入式的字符串,将SDS结构体嵌入RedisObject对象中”
,是专门用于保存短字符串的一种编码方式,与raw的差别在于,raw会调用两次内存分配函数来创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间内一次包含了redisObject和sdshdr两个结构。
embstr有以下好处:
- embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次,内存释放函数也是从两次降低为一次。
- 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面,所以这些编码的字符串对象比起raw编码的对象字符串,能够更好地利用缓存(CPU缓存/缓存行)带来的优势。
embstr的缺点:
- 如果字符串的长度增加需要重新分配内存时,sds需要重新分配空间,所以embstr编码的字符串对象实际上是只读的,redis没有为embstr编码的字符串对象编写任何相应的修改程序。当我们对embstr编码的字符串对象执行任何修改命令(例如append)时,程序会先将对象的编码从embstr转换成raw,然后再执行修改命令。
redis> SET msg hello
OK
redis> OBJECT ENCODING msg
embstr
redis> DEBUG OBJECT msg
Value at:0x7fd74ecac8a0 refcount:1 encoding:embstr serializedlength:6 lru:815344 lru_seconds_idle:14
redis> APPEND msg world
10
redis> OBJECT ENCODING msg
raw
redis> DEBUG OBJECT msg
Value at:0x7fd76445d0b0 refcount:1 encoding:raw serializedlength:11 lru:815482 lru_seconds_idle:26
为什么是“44”?
- redisObject = 16byte = type 4bit + encoding 4bit + lru 24bit + refcount 4byte + ptr 8byte。
- sdshdr=len 1byte + alloc 1byte + flag 1byte + '0' 1byte + buf长度。(3.2之前的版本)
-
从2.4版本开始,redis开始使用jemalloc内存分配器。可以简单理解,jemalloc不是一个一个字节来申请和分配的,会分配8,16,32,64等字节的内存(如需要12个字节,就会分配16个字节)。embstr最小为16+8+1=25,所以最小分配64字节。当字符数小于39时,都会分配64字节。这个默认39就是这样来的。
- jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。
- 例如:如果需要存储大小为130字节的对象,jemalloc会将其放入160字节的内存单元中。
- 下图图片来源:Redis容量评估模型
-
3.2版本之前是39,3.2开始是44,
- 3.2版本将原来的sdshdr改成了sdshdr8,sdshdr16,sdshdr32,sdshdr64,里面的unsigned int 变成了uint8_t,uint16_t...(还加了一个char flags)这样更加优化小sds的内存使用。
- 本身就是针对短字符串的embstr自然会使用最小的sdshdr8,而sdshdr8与之前的sdshdr相比正好减少了5个字节(sdsdr8 = uint8_t 2 + char = 12+1 = 3, sdshdr = unsigned int 2 = 4 2 = 8),所以其能容纳的字符串长度增加了5个字节变成了44。
- 本次变动的commit地址。
- embstr结构图:
3. int
如果一个字符串对象保存的是整数,并且这个整数值可以用long类型来标识(不超过long的范围),这时候字符串对象redisobject的指针将直接保存long数值(将void *转换成long)。
- 没有sdshdr对象。
- Long刚好跟指针的字节数一样(例如64位服务器下都是占8byte,“9223372036854775807”是8位字节可表示的最大整数,它的16进制形式是:
0x7fffffffffffffffL
,所以数值不能超过9223372036854775807
)。 - 取数据时将指针地址转为long值:(long)o->ptr。
// server.h
define OBJ_SHARED_INTEGERS 10000
// object.c
robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
robj *o;
if (server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
{
/* If the maxmemory policy permits, we can still return shared integers
* even if valueobj is true. */
valueobj = 0;
}
if (value >= 0 && value < OBJ_SHARED_INTEGERS && valueobj == 0) { // 10000以内直接用共享的对象
incrRefCount(shared.integers[value]);
o = shared.integers[value];
} else {
if (value >= LONG_MIN && value <= LONG_MAX) {
o = createObject(OBJ_STRING, NULL);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*)((long)value); // 直接将long值转为指针地址存储
} else {
o = createObject(OBJ_STRING,sdsfromlonglong(value));
}
}
return o;
}
以上内容参考自:
《redis设计与实现》
《Redis源码剖析系列》
《Redis内部数据结构详解系列》
《可能是目前最详细的Redis内存模型及应用解读》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。