redis數據結構(一)-前言

 基於redis5.0的版本。

前言

  • 假設咱們如今須要本身寫個緩存,常見的就是用HashMap<String, String>存儲。
  • 若是爲了方便統計、回收、或者其餘緣由,要記錄下緩存的最近訪問時間、建立時間等等,須要將值包裝成ValueObject,裏面包含了額外的擴展信息。
  • 若是緩存值不止是字符串,也多是List、Set等等,須要個標識字段來標識數據類型。 
  • 若是要給緩存加上過時時間:html

    1. 方式一:在ValueObject上加上過時時間字段。
    2. 方式二:再建一個對應的過時時間Map,Map的值爲過時時間。redis

      • 優勢:檢查過時緩存時,能夠更快的遍歷全部的內容;由於Map裏不會存在沒有設置過時時間的內容,且因爲值只有一個字段,能夠更快的完成遍歷。
      • 缺點:佔用更多的空間,可是可讓兩個Map共用key來節省部分空間。

RedisServer

在redis系統內部,有一個redisServer結構體的全局變量serverserver保存了redis服務端全部的信息,包括當前進程的PID、服務器的端口號、數據庫個數、統計信息等等。固然,它也包含了數據庫信息,包括數據庫的個數、以及一個redisDb數組。每個redisDb互相獨立,互不干擾。算法

struct redisServer {
    ……
    redisDb *db; 
    int dbnum; // Total number of configured DBs 
    ……
}

/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
    dict *dict; /* The keyspace for this DB */
    dict *expires; /* Timeout of keys with a timeout set */
    dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys; /* Blocked keys that received a PUSH */
    dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
    int id; /* Database ID */
    long long avg_ttl; /* Average TTL, just for stats */
    list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

SDS

redis並無直接使用c語言傳統的字符串,而是本身構建了SDS(simple dynamic string)。爲何不直接使用c語言的字符串?數據庫

  1. c語言字符串並不記錄自身的長度,若是要獲取字符串長度,須要遍歷整個字符串。
  2. 每次增加/縮短字符串時,都須要對字符串數組進行一次內存操做(申請/釋放內存),而光是執行內存重分配的時間,就會佔去修改字符串所用時間中的一大部分,對性能形成影響。
  3. c語言字符串以‘\0’字符結尾,若是存儲的數據字節自己包含‘\0’,就會對結果形成影響。
// sds.h
// 五種header類型,flags取值爲0~4
define SDS_TYPE_5 0
define SDS_TYPE_8 1
define SDS_TYPE_16 2
define SDS_TYPE_32 3
define SDS_TYPE_64 4
    
/* Note: sdshdr5 is never used, we just access the flags byte directly.
 * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
    uint8_t len; /* buf數組已使用的長度,不包含最後的結束符'\0' */
    uint8_t alloc; /* buf數組最大容量,不包含最後的結束符'\0' */
    unsigned char flags; /* 老是佔用一個字節,其中的最低3個bit用來表示header的類型。 */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
    uint16_t len; /* used */
    uint16_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
    uint32_t len; /* used */
    uint32_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
    uint64_t len; /* used */
    uint64_t alloc; /* excluding the header and null terminator */
    unsigned char flags; /* 3 lsb of type, 5 unused bits */
    char buf[];
};

一個sds字符串的完整結構,由在內存地址上先後相鄰的兩部分組成:數組

  1. 一個header。一般包含字符串的長度(len)最大容量(alloc)flagssdshdr5有所不一樣
  2. 一個字符數組。這個字符數組的長度等於最大容量+1。真正有效的字符串數據,其長度一般小於最大容量。在真正的字符串數據以後,是空餘未用的字節(通常以字節0填充),容許在不從新分配內存的前提下讓字符串數據向後作有限的擴展。在真正的字符串數據以後,還有一個NULL結束符,即ASCII碼爲0的'\0'字符。這是爲了和傳統C字符串兼容。之因此字符數組的長度比最大容量多1個字節,就是爲了在字符串長度達到最大容量時仍然有1個字節存放NULL結束。
    63484167.png

上圖是sds的一個內部結構的例子。圖中展現了兩個sds字符串s1和s2的內存結構,一個使用sdshdr8類型的header,另外一個使用sdshdr16類型的header。但它們都表達了一樣的一個長度爲6的字符串的值:"tielei"
sds的字符指針(s1和s2)就是指向真正的數據(字符數組)開始的位置,而header位於內存地址較低的方向。在sds.h中有一些跟解析header有關的宏定義。緩存

// sds.h
define SDS_TYPE_MASK 7
define SDS_TYPE_BITS 3
define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (void*)((s)-(sizeof(struct sdshdr##T)));
define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))))
define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS)

sds sdsnewlen(const void *init, size_t initlen) {
    void *sh;
    sds s;
    char type = sdsReqType(initlen);
    /* Empty strings are usually created in order to append. Use type 8
     * since type 5 is not good at this. */
    if (type == SDS_TYPE_5 && initlen == 0) type = SDS_TYPE_8;
    int hdrlen = sdsHdrSize(type);
    unsigned char *fp; /* flags pointer. */

    sh = s_malloc(hdrlen+initlen+1); // 額外的一個結束符字節
    if (init==SDS_NOINIT)
        init = NULL;
    else if (!init)
        memset(sh, 0, hdrlen+initlen+1);
    if (sh == NULL) return NULL;
    s = (char*)sh+hdrlen;
    fp = ((unsigned char*)s)-1;
    switch(type) {
        case SDS_TYPE_5: {
            *fp = type | (initlen << SDS_TYPE_BITS);
            break;
        }
        case SDS_TYPE_8: {
            SDS_HDR_VAR(8,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_16: {
            SDS_HDR_VAR(16,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_32: {
            SDS_HDR_VAR(32,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
        case SDS_TYPE_64: {
            SDS_HDR_VAR(64,s);
            sh->len = initlen;
            sh->alloc = initlen;
            *fp = type;
            break;
        }
    }
    if (initlen && init)
        memcpy(s, init, initlen);
    s[initlen] = '\0';
    return s;
}    

/* Create an empty (zero length) sds string. Even in this case the string
 * always has an implicit null term. */
sds sdsempty(void) {
    return sdsnewlen("",0);
}

/* Create a new sds string starting from a null terminated C string. */
sds sdsnew(const char *init) {
    size_t initlen = (init == NULL) ? 0 : strlen(init);
    return sdsnewlen(init, initlen);
}

/* Duplicate an sds string. */
sds sdsdup(const sds s) {
    return sdsnewlen(s, sdslen(s));
}

static inline size_t sdslen(const sds s) {
    unsigned char flags = s[-1];
    switch(flags&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return SDS_TYPE_5_LEN(flags);
        case SDS_TYPE_8:
            return SDS_HDR(8,s)->len;
        case SDS_TYPE_16:
            return SDS_HDR(16,s)->len;
        case SDS_TYPE_32:
            return SDS_HDR(32,s)->len;
        case SDS_TYPE_64:
            return SDS_HDR(64,s)->len;
    }
    return 0;
}

/* Free an sds string. No operation is performed if 's' is NULL. */
void sdsfree(sds s) {
    if (s == NULL) return;
    s_free((char*)s-sdsHdrSize(s[-1]));
}

// sds.c
static inline int sdsHdrSize(char type) {
    switch(type&SDS_TYPE_MASK) {
        case SDS_TYPE_5:
            return sizeof(struct sdshdr5);
        case SDS_TYPE_8:
            return sizeof(struct sdshdr8);
        case SDS_TYPE_16:
            return sizeof(struct sdshdr16);
        case SDS_TYPE_32:
            return sizeof(struct sdshdr32);
        case SDS_TYPE_64:
            return sizeof(struct sdshdr64);
    }
    return 0;
}

static inline char sdsReqType(size_t string_size) {
    if (string_size < 1<<5) // 32
        return SDS_TYPE_5;
    if (string_size < 1<<8) // 256
        return SDS_TYPE_8;
    if (string_size < 1<<16) // 64K
        return SDS_TYPE_16;
#if (LONG_MAX == LLONG_MAX)
    if (string_size < 1ll<<32) // 4GB
        return SDS_TYPE_32;
    return SDS_TYPE_64;
#else
    return SDS_TYPE_32;
#endif

其中SDS_HDR用來從sds字符串得到header起始位置的指針,好比SDS_HDR(8, s1)表示s1header指針,SDS_HDR(16, s2)表示s2header指針。服務器

sdsnewlen建立一個長度爲initlen的sds字符串,並使用init指向的字符數組(任意二進制數據)來初始化數據。若是initNULL,那麼使用全0來初始化數據。它的實現中,咱們須要注意的是:數據結構

  • 若是要建立一個長度爲0的空字符串,那麼不使用SDS_TYPE_5類型的header,而是轉而使用SDS_TYPE_8類型的header。這是由於建立的空字符串通常接下來的操做極可能是追加數據,但SDS_TYPE_5類型的sds字符串不適合追加數據(會引起內存從新分配)。
  • 須要的內存空間一次性進行分配,其中包含三部分:header數據最後的多餘字節(hdrlen+initlen+1)。
  • 初始化的sds字符串數據最後會追加一個NULL結束符(s[initlen] = ‘\0’)。  

固然,使用SDS_HDR以前咱們必須先知道究竟是哪種header,這樣咱們才知道SDS_HDR第1個參數應該傳什麼。由sds字符指針得到header類型的方法是,先向低地址方向偏移1個字節的位置,獲得flags字段。好比,s1[-1]和s2[-1]分別得到了s1和s2的flags的值。而後取flags的最低3個bit獲得header的類型。app

  • 因爲s1[-1] == 0x01 == SDS_TYPE_8,所以s1的header類型是sdshdr8。
  • 因爲s2[-1] == 0x02 == SDS_TYPE_16,所以s2的header類型是sdshdr16。   

有了header指針,就能很快定位到它的len和alloc字段:ide

  • s1的header中,len的值爲0x06,表示字符串數據長度爲6;alloc的值爲0x80,表示字符數組最大容量爲128
  • s2的header中,len的值爲0x0006,表示字符串數據長度爲6;alloc的值爲0x03E8,表示字符數組最大容量爲1000(注意:圖中是按小端地址構成)    

在各個header的類型定義中,還有幾個須要咱們注意的地方:

  • 在各個header的定義中使用了__attribute__ ((packed)),是爲了讓編譯器以緊湊模式來分配內存(按實際佔用字節數進行對齊)。若是沒有這個屬性,編譯器可能會爲struct的字段作優化對齊,在其中填充空字節。那樣的話,就不能保證header和sds的數據部分牢牢先後相鄰,也不能按照固定向低地址方向偏移1個字節的方式來獲取flags字段了。
  • 在各個header的定義中最後有一個char buf[]。咱們注意到這是一個沒有指明長度的字符數組,這是C語言中定義字符數組的一種特殊寫法,稱爲柔性數組(flexible array member),只能定義在一個結構體的最後一個字段上。它在這裏只是起到一個標記的做用,表示在flags字段後面就是一個字符數組,或者說,它指明瞭緊跟在flags字段後面的這個字符數組在結構體中的偏移位置。而程序在爲header分配的內存的時候,它並不佔用內存空間。若是計算sizeof(struct sdshdr16)的值,那麼結果是5個字節,其中沒有buf字段。
  • sdshdr5與其它幾個header結構不一樣,它不包含alloc字段,而長度使用flags的高5位來存儲。所以,它不能爲字符串分配空餘空間。若是字符串須要動態增加,那麼它就必然要從新分配內存才行。因此說,這種類型的sds字符串更適合存儲靜態的短字符串(長度小於32)。    

至此,咱們很是清楚地看到了:sds字符串的header,其實隱藏在真正的字符串數據的前面(低地址方向/小端)。這樣的一個定義,有以下幾個好處:

  • header和數據相鄰,而不用分紅兩塊內存空間來單獨分配。這有利於減小內存碎片,提升存儲效率(memory efficiency)。
  • 雖然header有多個類型,但sds能夠用統一的char *來表達。且它與傳統的C語言字符串保持類型兼容。若是一個sds裏面存儲的是可打印字符串,那麼咱們能夠直接把它傳給C函數,好比使用strcmp比較字符串大小,或者使用printf進行打印。

sds相對c語言字符串的優勢:

  1. SDS能夠經過len快速得到字符串長度,便於統計。
  2. SDS能夠經過len判斷字符串是否結束,雖然數據庫通常用於保存穩步數據,但保存二進制的常見也很多見,若是二進制數據中也存在'\0'字符,用c語言字符串就會出現異常問題(只能取到第一個'\0'以前的字符)。
  3. SDS會兼容部分c語言函數,因此同樣遵循以'\0'字符結尾對慣例,爲了讓SDS可用重用部分<string.h>庫定義對函數。
  4. 當SDS API須要對SDS進行修改時,API會先檢查SDS的空間是否知足修改所需的要求,若是不知足,API會自動將SDS的空間擴展至執行所需的大小,而後才執行實際的修改:

    • 若是對SDS進行修改以後,SDS的長度(len屬性值)小於1MB,那麼程序會分配和len屬性一樣大小的未使用空間。這時候SDS的len屬性將和free屬性同樣,buf數組實際的長度將是len+free+1字節。
    • 若是對SDS進行修改以後,SDS的長度大於等於1MB,那麼程序會分配1MB的未使用空間。
    • 惰性空間釋放:當SDS的API須要縮短SDS保存的字符串時,程序並不當即回收縮短後多出來的字節,避免了縮短字符串時所需的內存從新分配操做,併爲未來可能有的增加操做提供了優化。與此同時,SDS也提供了相應的API,讓咱們能夠在有須要的時候,真正的釋放空閒的內存,因此不用擔憂惰性空間釋放會形成內存浪費。
// sds.h
define SDS_MAX_PREALLOC (1024*1024)

// sds.c
/* Enlarge the free space at the end of the sds string so that the caller
 * is sure that after calling this function can overwrite up to addlen
 * bytes after the end of the string, plus one more byte for nul term.
 *
 * Note: this does not change the *length* of the sds string as returned
 * by sdslen(), but only the free buffer space we have. */
sds sdsMakeRoomFor(sds s, size_t addlen) {
    void *sh, *newsh;
    size_t avail = sdsavail(s);
    size_t len, newlen;
    char type, oldtype = s[-1] & SDS_TYPE_MASK;
    int hdrlen;

    /* Return ASAP if there is enough space left. */
    if (avail >= addlen) return s;

    len = sdslen(s);
    sh = (char*)s-sdsHdrSize(oldtype);
    newlen = (len+addlen);
    if (newlen < SDS_MAX_PREALLOC) /* 小於1M */
        newlen *= 2;
    else
        newlen += SDS_MAX_PREALLOC;

    type = sdsReqType(newlen);

    /* Don't use type 5: the user is appending to the string and type 5 is
     * not able to remember empty space, so sdsMakeRoomFor() must be called
     * at every appending operation. */
    if (type == SDS_TYPE_5) type = SDS_TYPE_8;

    hdrlen = sdsHdrSize(type);
    if (oldtype==type) {
        newsh = s_realloc(sh, hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        s = (char*)newsh+hdrlen;
    } else {
        /* Since the header size changes, need to move the string forward,
         * and can't use realloc */
        newsh = s_malloc(hdrlen+newlen+1);
        if (newsh == NULL) return NULL;
        memcpy((char*)newsh+hdrlen, s, len+1);
        s_free(sh);
        s = (char*)newsh+hdrlen;
        s[-1] = type;
        sdssetlen(s, len);
    }
    sdssetalloc(s, newlen);
    return s;
}

sdsMakeRoomFor是sds實現中很重要的一個函數。關於它的實現代碼,咱們須要注意的是:

  • 若是原來字符串中的空餘空間夠用(avail >= addlen),那麼它什麼也不作,直接返回。
  • 若是須要分配空間,它會比實際請求的要多分配一些,以防備接下來繼續追加。它在字符串已經比較長的狀況下至多分配SDS_MAX_PREALLOC個字節,這個常量在sds.h中定義爲(1024*1024)=1MB
  • 按分配後的空間大小,可能須要更換header類型(原來header的alloc字段過短,表達不了增長後的容量)。
  • 若是須要更換header,那麼整個字符串空間(包括header)都須要從新分配(s_malloc),並拷貝原來的數據到新的位置。
  • 若是不須要更換header(原來的header夠用),那麼調用一個比較特殊的s_realloc,試圖在原來的地址上從新分配空間。s_realloc的具體實現得看Redis編譯的時候選用了哪一個allocator(在Linux上默認使用jemalloc)。但無論是哪一個realloc的實現,它所表達的含義基本是相同的:它儘可能在原來分配好的地址位置從新分配,若是原來的地址位置有足夠的空餘空間完成從新分配,那麼它返回的新地址與傳入的舊地址相同;不然,它分配新的地址塊,並進行數據搬遷

RedisObject

一個database內的這個映射關係是用一個dict來維護的。dictkey固定用一種數據結構來表達就夠了,這就是動態字符串sds。而value則比較複雜,爲了在同一個dict內可以存儲不一樣類型的value,這就須要一個通用的數據結構,這個通用的數據結構就是redisObject

// server.h
// type
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */
// encoding
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

struct redisObject {
    unsigned type:4; // 4bit,對象類型,值包括string,hash,list,set,zset等,能夠用type {key}命令查看
    unsigned encoding:4; // 4bit,編碼類型,如字符串類型的OBJ_ENCODING_RAW,OBJ_ENCODING_INT,OBJ_ENCODING_EMBSTR,能夠用object encoding {key}命令查看
    unsigned lru:LRU_BITS; // LRU_BITS 佔24bit,記錄對象最後一次被訪問的時間,作LRU替換算法用,能夠用object idletime {key}命令查看
    int refcount; // 記錄當前對象被引用數,用於經過引用數回收內存,能夠用object refcount {key}命令查看
    void *ptr; // 數據指針。指向真正的數據。好比,一個表明string的robj,它的ptr可能指向一個sds結構;一個表明list的robj,它的ptr可能指向一個quicklist。
};

refcount

  • 對象回收:C語言並不具有自動內存回收功能,Redis在本身的對象系統中構建了一個引用計數技術實現的內存回收機制,經過這一機制,程序能夠經過跟蹤對象的引用計數信息,在適當的時候自動釋放對象並進行內存回收。每一個對象的引用計數信息由redis對象結構的refcount屬性記錄,建立一個新對象時,引用計數值會初始化爲1;對象被一個新程序使用時,它的引用計數值會被增1;再也不被一個程序使用時減1;引用計數值變爲0,對象所佔用的內存會被釋放。Redis的del命令就依賴decrRefCount操做將value釋放。
// object.c
void decrRefCount(robj *o) {
    if (o->refcount == 1) {
        switch(o->type) {
        case OBJ_STRING: freeStringObject(o); break;
        case OBJ_LIST: freeListObject(o); break;
        case OBJ_SET: freeSetObject(o); break;
        case OBJ_ZSET: freeZsetObject(o); break;
        case OBJ_HASH: freeHashObject(o); break;
        case OBJ_MODULE: freeModuleObject(o); break;
        case OBJ_STREAM: freeStreamObject(o); break;
        default: serverPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
        if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
    }
}
  • 對象共享:對象的引用計數屬性還帶有對象共享的做用。

    • 在string對象中,在建立一個數字時,會判斷是否在shared.integers(默認0-9999)的範圍中(實際上,生產的數字的引用次數refcount固定爲2147483647),若是命中就不進行對象建立,直接使用對應的共享對象,並將引用計數加一。
    • 如圖展現了包含整數值100的字符串對象同時被鍵A和鍵B共享以後的樣子,能夠看到,除了對象的引用計數從以前的1變成了2以外, 其餘屬性都沒有變化(理論上是這樣,但因爲數字的引用次數固定,因此實際上計數並不會有這樣的變化)。
      a263f699-b26c-45d5-8b4b-c1ae18001188.png
    • Redis爲何只對包含整數值得字符串對象進行共享?

      • 當服務器考慮將一個共享對象設置爲鍵的值對象時,程序須要先檢查給定的共享對象和鍵想建立的目標對象是否徹底相同。一個共享對象保存的值越複雜,驗證共享對象和目標對象是否相同所需的複雜度就會越高,消耗的CPU時間也會越多。
      • 若是共享對象是保存整數值的字符串對象,驗證操做的複雜度O(1)
      • 若是共享對象是保存字符串值的字符串對象,驗證操做的複雜度O(N)
      • 若是共享對象是包含多個值(好比列表對象或者哈希對象)對象,驗證操做的複雜度O(N2)。·
      • 所以,儘管共享更復雜的對象能夠節約更多的內存,但受到CPU時間的限制,Redis只對包含整數值得字符串對象進行共享。
    • 須要提醒的是,appendsetbit命令會解除對象的共享狀態,例如:

      • 執行set testref 1命令以後,再經過object debug testref, 能夠看到refcount:2147483647
      • 執行append testref 1命令以後,再經過object debug testref, 能夠看到refcount:1
// server.h
#define OBJ_SHARED_REFCOUNT INT_MAX
#define OBJ_SHARED_INTEGERS 10000
// object.c
robj *makeObjectShared(robj *o) {
    serverAssert(o->refcount == 1);
    o->refcount = OBJ_SHARED_REFCOUNT;
    return o;
}
// server.c
void createSharedObjects(void) {
 ……
    for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
        shared.integers[j] =
            makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
        shared.integers[j]->encoding = OBJ_ENCODING_INT;
    }    
    ……    
}    
// t_string.c
void appendCommand(client *c) {
    ……
   o = dbUnshareStringValue(c->db,c->argv[1],o);
   ……
}
// db.c
robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
    serverAssert(o->type == OBJ_STRING);
    if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {
        robj *decoded = getDecodedObject(o);
        o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));
        decrRefCount(decoded);
        dbOverwrite(db,key,o);
    }
    return o;
}

redisObject的結構與對象類型、編碼、內存回收、共享對象都有關係;一個redisObject對象的大小爲16字節:4bit+4bit+24bit+4Byte+8Byte=16Byte

以上內容參考自:
《redis設計與實現》
Redis源碼剖析系列
Redis內部數據結構詳解系列
多是目前最詳細的Redis內存模型及應用解讀
相關文章
相關標籤/搜索