redis数据结构(二) - 字符串

基于redis5.0的版本。

字符串对象的编码有:int,raw或者embstr。

1.raw

raw就是redisObject+sds,即redisObjectptr指针指向一个sds对象。
redis数据结构(二) - 字符串_第1张图片

// 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”?

  1. redisObject = 16byte = type 4bit + encoding 4bit + lru 24bit + refcount 4byte + ptr 8byte。
  2. sdshdr=len 1byte + alloc 1byte + flag 1byte + '0' 1byte + buf长度。(3.2之前的版本)
  3. 从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容量评估模型
      redis数据结构(二) - 字符串_第2张图片
  4. 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地址
  5. embstr结构图:

redis数据结构(二) - 字符串_第3张图片

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,数据结构)