redis數據結構(二) - 字符串

基於redis5.0的版本。

字符串編碼:字符串對象的編碼能夠是int,raw或者embstr。git

1.raw

raw就是redisObject+sds,即redisObjectptr指針指向一個sds對象。
78419626.pnggithub

// 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);
}

robj *createRawStringObject(const char *ptr, size_t len) {
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}

/* 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有如下好處:redis

  • 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以前的版本)服務器

    • 自己就是針對短字符串的embstr天然會使用最小的sdshdr8。
  3. 從2.4版本開始,redis開始使用jemalloc內存分配器。能夠簡單理解,jemalloc不是一個一個字節來申請和分配的,會分配8,16,32,64等字節的內存(如須要12個字節,就會分配16個字節)。embstr最小爲16+8+1=25,因此最小分配64字節。當字符數小於39時,都會分配64字節。這個默認39就是這樣來的。app

    • jemalloc做爲Redis的默認內存分配器,在減少內存碎片方面作的相對比較好。jemalloc在64位系統中,將內存空間劃分爲小、大、巨大三個範圍;每一個範圍內又劃分了許多小的內存塊單位;當Redis存儲數據時,會選擇大小最合適的內存塊進行存儲。
    • 例如:若是須要存儲大小爲130字節的對象,jemalloc會將其放入160字節的內存單元中。
    • 下圖圖片來源:Redis容量評估模型
      62832053.png
  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結構圖:

78516328.png

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;
}
相關文章
相關標籤/搜索