Redis源碼剖析和註釋(八)--- 對象系統(redisObject)

Redis 對象系統

1. 介紹

redis中基於雙端鏈表、簡單動態字符串(sds)、字典、跳躍表、整數集合、壓縮列表、快速列表等等數據結構實現了一個對象系統,並且實現了5種不同的對象,每種對象都使用了至少一種前面的數據結構,優化對象在不同場合下的使用效率。

2. 對象的系統的實現

redis 3.2版本。所有註釋在github中:對象系統的註釋

2.1 對象的結構

對象結構robj功能:

  • 爲5種不同的對象類型提供同一的表示形式。
  • 爲不同的對象適用於不同的場景,支持同一種對象類型採用多種的數據結構方式。
  • 支持引用計數,實現對象共享機制。
  • 記錄對象的訪問時間,便於刪除對象。

對象結構定義在redis 3.2版本的server.h

#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

typedef struct redisObject {
    //對象的數據類型,佔4bits,共5種類型
    unsigned type:4;        
    //對象的編碼類型,佔4bits,共10種類型
    unsigned encoding:4;

    //least recently used
    //實用LRU算法計算相對server.lruclock的LRU時間
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */

    //引用計數
    int refcount;

    //指向底層數據實現的指針
    void *ptr;
} robj;

//type的佔5種類型:
/* Object types */
#define OBJ_STRING 0    //字符串對象
#define OBJ_LIST 1      //列表對象
#define OBJ_SET 2       //集合對象
#define OBJ_ZSET 3      //有序集合對象
#define OBJ_HASH 4      //哈希對象

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
// encoding 的10種類型
#define OBJ_ENCODING_RAW 0     /* Raw representation */     //原始表示方式,字符串對象是簡單動態字符串
#define OBJ_ENCODING_INT 1     /* Encoded as integer */         //long類型的整數
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */      //字典
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */          //不在使用
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */  //雙端鏈表,不在使用
#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 */   //embstr編碼的簡單動態字符串
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */   //由壓縮列表組成的雙向列表-->快速列表

2.2 字符串對象的底層實現類型

編碼—encoding 對象—ptr
OBJ_ENCODING_RAW 簡單動態字符串實現的字符串對象
OBJ_ENCODING_INT 整數值實現的字符串對象
OBJ_ENCODING_EMBSTR embstr編碼的簡單動態字符串實現的字符串對象

2.3 列表對象的底層實現類型

編碼—encoding 對象—ptr
OBJ_ENCODING_QUICKLIST 快速列表實現的列表對象
OBJ_ENCODING_ZIPLIST 壓縮列表實現的列表對象

2.4 集合對象的底層實現類型

編碼—encoding 對象—ptr
OBJ_ENCODING_HT 字典實現的集合對象
OBJ_ENCODING_INTSET 整數集合實現的集合對象

2.5 哈希對象的底層實現類型

編碼—encoding 對象—ptr
OBJ_ENCODING_ZIPLIST 壓縮列表實現的哈希對象
OBJ_ENCODING_HT 字典實現的哈希對象

2.6 有序集合對象的底層實現類型

編碼—encoding 對象—ptr
OBJ_ENCODING_SKIPLIST 跳躍表和字典實現的有序集合對象
OBJ_ENCODING_ZIPLIST 壓縮列表實現的有序集合對象

3. 對象系統的重要操作

3.1創建一個字符串對象

  • 編碼爲OBJ_ENCODING_RAW
robj *createObject(int type, void *ptr) {   //創建一個對象
    robj *o = zmalloc(sizeof(*o));          //分配空間
    o->type = type;                         //設置對象類型
    o->encoding = OBJ_ENCODING_RAW;         //設置編碼方式爲OBJ_ENCODING_RAW
    o->ptr = ptr;                           //設置
    o->refcount = 1;                        //引用計數爲1

    /* Set the LRU to the current lruclock (minutes resolution). */
    o->lru = LRU_CLOCK();                   //計算設置當前LRU時間
    return o;
}
  • 編碼爲OBJ_ENCODING_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. */
//創建一個embstr編碼的字符串對象
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+1剛好就是struct sdshdr8的地址

    o->type = OBJ_STRING;               //類型爲字符串對象
    o->encoding = OBJ_ENCODING_EMBSTR;  //設置編碼類型OBJ_ENCODING_EMBSTR
    o->ptr = sh+1;                      //指向分配的sds對象,分配的len+1的空間首地址
    o->refcount = 1;                    //設置引用計數
    o->lru = LRU_CLOCK();               //計算設置當前LRU時間

    sh->len = len;                      //設置字符串長度
    sh->alloc = len;                    //設置最大容量
    sh->flags = SDS_TYPE_8;             //設置sds的類型
    if (ptr) {                          //如果傳了字符串參數
        memcpy(sh->buf,ptr,len);        //將傳進來的ptr保存到對象中
        sh->buf[len] = '\0';            //結束符標誌
    } else {
        memset(sh->buf,0,len+1);        //否則將對象的空間初始化爲0
    }
    return o;
}
  • 兩種字符串對象編碼方式的區別
/* Create a string object with EMBSTR encoding if it is smaller than
 * REIDS_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 39 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */

//sdshdr8的大小爲3個字節,加上1個結束符共4個字節
//redisObject的大小爲16個字節
//redis使用jemalloc內存分配器,且jemalloc會分配8,16,32,64等字節的內存
//一個embstr固定的大小爲16+3+1 = 20個字節,因此一個最大的embstr字符串爲64-20 = 44字節
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44

// 創建字符串對象,根據長度使用不同的編碼類型
// createRawStringObject和createEmbeddedStringObject的區別是:
// createRawStringObject是當字符串長度大於44字節時,robj結構和sdshdr結構在內存上是分開的
// createEmbeddedStringObject是當字符串長度小於等於44字節時,robj結構和sdshdr結構在內存上是連續的
robj *createStringObject(const char *ptr, size_t len) {
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

3.2 字符串對象編碼的優化

/* Try to encode a string object in order to save space */
//嘗試優化字符串對象的編碼方式以節約空間
robj *tryObjectEncoding(robj *o) {
    long value;
    sds s = o->ptr;
    size_t len;

    /* Make sure this is a string object, the only type we encode
     * in this function. Other types use encoded memory efficient
     * representations but are handled by the commands implementing
     * the type. */
    serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);

    /* We try some specialized encoding only for objects that are
     * RAW or EMBSTR encoded, in other words objects that are still
     * in represented by an actually array of chars. */
    //如果字符串對象的編碼類型爲RAW或EMBSTR時,纔對其重新編碼
    if (!sdsEncodedObject(o)) return o;

    /* It's not safe to encode shared objects: shared objects can be shared
     * everywhere in the "object space" of Redis and may end in places where
     * they are not handled. We handle them only as values in the keyspace. */
    //如果refcount大於1,則說明對象的ptr指向的值是共享的,不對共享對象進行編碼
     if (o->refcount > 1) return o;

    /* Check if we can represent this string as a long integer.
     * Note that we are sure that a string larger than 20 chars is not
     * representable as a 32 nor 64 bit integer. */
    len = sdslen(s);            //獲得字符串s的長度

    //如果len小於等於20,表示符合long long可以表示的範圍,且可以轉換爲long類型的字符串進行編碼
    if (len <= 20 && string2l(s,len,&value)) {
        /* This object is encodable as a long. Try to use a shared object.
         * Note that we avoid using shared integers when maxmemory is used
         * because every object needs to have a private LRU field for the LRU
         * algorithm to work well. */
        if ((server.maxmemory == 0 ||
             (server.maxmemory_policy != MAXMEMORY_VOLATILE_LRU &&
              server.maxmemory_policy != MAXMEMORY_ALLKEYS_LRU)) &&
            value >= 0 &&
            value < OBJ_SHARED_INTEGERS)    //如果value處於共享整數的範圍內
        {
            decrRefCount(o);                //原對象的引用計數減1,釋放對象
            incrRefCount(shared.integers[value]); //增加共享對象的引用計數
            return shared.integers[value];      //返回一個編碼爲整數的字符串對象
        } else {        //如果不處於共享整數的範圍
            if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);   //釋放編碼爲OBJ_ENCODING_RAW的對象
            o->encoding = OBJ_ENCODING_INT;     //轉換爲OBJ_ENCODING_INT編碼
            o->ptr = (void*) value;             //指針ptr指向value對象
            return o;
        }
    }

    /* If the string is small and is still RAW encoded,
     * try the EMBSTR encoding which is more efficient.
     * In this representation the object and the SDS string are allocated
     * in the same chunk of memory to save space and cache misses. */
    //如果len小於44,44是最大的編碼爲EMBSTR類型的字符串對象長度
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
        robj *emb;

        if (o->encoding == OBJ_ENCODING_EMBSTR) return o;   //將RAW對象轉換爲OBJ_ENCODING_EMBSTR編碼類型
        emb = createEmbeddedStringObject(s,sdslen(s)); //創建一個編碼類型爲OBJ_ENCODING_EMBSTR的字符串對象
        decrRefCount(o);    //釋放之前的對象
        return emb;
    }

    /* We can't encode the object...
     *
     * Do the last try, and at least optimize the SDS string inside
     * the string object to require little space, in case there
     * is more than 10% of free space at the end of the SDS string.
     *
     * We do that only for relatively large strings as this branch
     * is only entered if the length of the string is greater than
     * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
    //無法進行編碼,但是如果s的未使用的空間大於使用空間的10分之1
    if (o->encoding == OBJ_ENCODING_RAW &&
        sdsavail(s) > len/10)
    {
        o->ptr = sdsRemoveFreeSpace(o->ptr);    //釋放所有的未使用空間
    }

    /* Return the original object. */
    return o;
}

3.3 引用計數管理對象

//引用計數加1
void incrRefCount(robj *o) {
    o->refcount++;
}

//引用計數減1
void decrRefCount(robj *o) {
    if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");

    //當引用對象等於1時,在操作引用計數減1,直接釋放對象的ptr和對象空間
    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;
        default: serverPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
        o->refcount--;  //否則減1
    }
}

3.4 對象的複製,創建的對象非共享

//返回 複製的o對象的副本的地址,且創建的對象非共享
robj *dupStringObject(robj *o) {
    robj *d;

    serverAssert(o->type == OBJ_STRING);    //一定是OBJ_STRING類型

    switch(o->encoding) {                   //根據不同的編碼類型
    case OBJ_ENCODING_RAW:
        return createRawStringObject(o->ptr,sdslen(o->ptr));        //創建的對象非共享
    case OBJ_ENCODING_EMBSTR:
        return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));   //創建的對象非共享
    case OBJ_ENCODING_INT:                  //整數編碼類型
        d = createObject(OBJ_STRING, NULL); //即使是共享整數範圍內的整數,創建的對象也是非共享的
        d->encoding = OBJ_ENCODING_INT;
        d->ptr = o->ptr;
        return d;
    default:
        serverPanic("Wrong encoding.");
        break;
    }
}

3.5 對象的解碼操作

將保存的整數值解碼成字符串對象返回回來。

/* Get a decoded version of an encoded object (returned as a new object).
 * If the object is already raw-encoded just increment the ref count. */
//將對象是整型的解碼爲字符串並返回,如果是字符串編碼則直接返回輸入對象,只需增加引用計數
robj *getDecodedObject(robj *o) {
    robj *dec;

    if (sdsEncodedObject(o)) {  //如果是OBJ_ENCODING_RAW或OBJ_ENCODING_EMBSTR類型的對象
        incrRefCount(o);        //增加引用計數,返回一個共享的對象
        return o;
    }
    if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) { //如果是整數對象
        char buf[32];

        ll2string(buf,32,(long)o->ptr); //將整數轉換爲字符串
        dec = createStringObject(buf,strlen(buf));  //創建一個字符串對象
        return dec;
    } else {
        serverPanic("Unknown encoding type");
    }
}

3.6 其他操作

所有註釋在github中:對象系統的註釋

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章