1. 程式人生 > >Redis之object原始碼閱讀

Redis之object原始碼閱讀

在這片部落格中,我們將對object.c這個檔案的其中一些方法進行閱讀,大部分程式碼都是類似的: createObject:建立一個新物件

robj *createObject(int type, void *ptr) {

    robj *o = zmalloc(sizeof(*o));

    o->type = type;
    o->encoding = REDIS_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution). */
    o->lru = LRU_CLOCK();
    return o;
}

傳入我們要建立的物件的型別,和資料結構的引用,返回一個建立的物件 createRawStringObject:建立一個raw編碼格式的字串物件

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

createEmbeddedStringObject:建立一個embstr編碼格式的字串物件

robj *createEmbeddedStringObject(char *ptr, size_t len) {
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
    struct sdshdr *sh = (void*)(o+1);

    o->type = REDIS_STRING;
    o->encoding = REDIS_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    o->lru = LRU_CLOCK();

    sh->len = len;
    sh->free = 0;
    if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

從這裡我們就可以看出embstr編碼格式的字串物件在申請空間的時候同時將sds的空間一起申請了 createStringObject:建立一個字串物件

robj *createStringObject(char *ptr, size_t len) {
    if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

在建立按字串物件的時候會根據字串的長度建立不同編碼方式的物件 createStringObjectFromLongLong:根據傳入的整數值建立一個字串物件

robj *createStringObjectFromLongLong(long long value) {

    robj *o;

    // value 的大小符合 REDIS 共享整數的範圍
    // 那麼返回一個共享物件
    if (value >= 0 && value < REDIS_SHARED_INTEGERS) {
        incrRefCount(shared.integers[value]);
        o = shared.integers[value];

    // 不符合共享範圍,建立一個新的整數物件
    } else {
        // 值可以用 long 型別儲存,
        // 建立一個 REDIS_ENCODING_INT 編碼的字串物件
        if (value >= LONG_MIN && value <= LONG_MAX) {
            o = createObject(REDIS_STRING, NULL);
            o->encoding = REDIS_ENCODING_INT;
            o->ptr = (void*)((long)value);

        // 值不能用 long 型別儲存(long long 型別),將值轉換為字串,
        // 並建立一個 REDIS_ENCODING_RAW 的字串物件來儲存值
        } else {
            o = createObject(REDIS_STRING,sdsfromlonglong(value));
        }
    }

    return o;
}

首先會判斷這個整數是否在共享物件的範圍內,如果在直接返回這個共享物件,否則判斷這個整數能否被long型別儲存,能儲存就建立編碼格式為int的字串物件,否則建立一個raw型別的物件。 createStringObjectFromLongDouble:根據long double型別建立一個字元傳物件

robj *createStringObjectFromLongDouble(long double value) {
    char buf[256];
    int len;
    // 使用 17 位小數精度,這種精度可以在大部分機器上被 rounding 而不改變
    len = snprintf(buf,sizeof(buf),"%.17Lf", value);

    /* Now remove trailing zeroes after the '.' */
    // 移除尾部的 0 
    // 比如 3.1400000 將變成 3.14
    // 而 3.00000 將變成 3
    if (strchr(buf,'.') != NULL) {
        char *p = buf+len-1;
        while(*p == '0') {
            p--;
            len--;
        }
        // 如果不需要小數點,那麼移除它
        if (*p == '.') len--;
    }

    // 建立物件
    return createStringObject(buf,len);
}

dupStringObject:複製一個字串物件

robj *dupStringObject(robj *o) {
    robj *d;

    redisAssert(o->type == REDIS_STRING);

    switch(o->encoding) {

    case REDIS_ENCODING_RAW:
        return createRawStringObject(o->ptr,sdslen(o->ptr));

    case REDIS_ENCODING_EMBSTR:
        return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));

    case REDIS_ENCODING_INT:
        d = createObject(REDIS_STRING, NULL);
        d->encoding = REDIS_ENCODING_INT;
        d->ptr = o->ptr;
        return d;

    default:
        redisPanic("Wrong encoding.");
        break;
    }
}

createListObject:建立建立一個 LINKEDLIST 編碼的列表物件

robj *createListObject(void) {

    list *l = listCreate();

    robj *o = createObject(REDIS_LIST,l);

    listSetFreeMethod(l,decrRefCountVoid);

    o->encoding = REDIS_ENCODING_LINKEDLIST;

    return o;
}

createZiplistObject:建立一個 ZIPLIST 編碼的列表物件

robj *createZiplistObject(void) {

    unsigned char *zl = ziplistNew();

    robj *o = createObject(REDIS_LIST,zl);

    o->encoding = REDIS_ENCODING_ZIPLIST;

    return o;
}

createSetObject:建立一個set編碼的集合物件

robj *createSetObject(void) {

    dict *d = dictCreate(&setDictType,NULL);

    robj *o = createObject(REDIS_SET,d);

    o->encoding = REDIS_ENCODING_HT;

    return o;
}
/*
 * 建立一個 INTSET 編碼的集合物件
 */
robj *createIntsetObject(void) {

    intset *is = intsetNew();

    robj *o = createObject(REDIS_SET,is);

    o->encoding = REDIS_ENCODING_INTSET;

    return o;
}
/*
 * 建立一個 ZIPLIST 編碼的雜湊物件
 */
robj *createHashObject(void) {

    unsigned char *zl = ziplistNew();

    robj *o = createObject(REDIS_HASH, zl);

    o->encoding = REDIS_ENCODING_ZIPLIST;

    return o;
}

/*
 * 建立一個 SKIPLIST 編碼的有序集合
 */
robj *createZsetObject(void) {

    zset *zs = zmalloc(sizeof(*zs));

    robj *o;

    zs->dict = dictCreate(&zsetDictType,NULL);
    zs->zsl = zslCreate();

    o = createObject(REDIS_ZSET,zs);

    o->encoding = REDIS_ENCODING_SKIPLIST;

    return o;
}

我們發現SKIPLIST 編碼的有序集合同時使用了跳躍表和字典兩種資料結構來實現

/*
 * 建立一個 ZIPLIST 編碼的有序集合
 */
robj *createZsetZiplistObject(void) {

    unsigned char *zl = ziplistNew();

    robj *o = createObject(REDIS_ZSET,zl);

    o->encoding = REDIS_ENCODING_ZIPLIST;

    return o;
}
/*
 * 為物件的引用計數增一
 */
void incrRefCount(robj *o) {
    o->refcount++;
}

/*
 * 為物件的引用計數減一
 *
 * 當物件的引用計數降為 0 時,釋放物件。
 */
void decrRefCount(robj *o) {

    if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");

    // 釋放物件
    if (o->refcount == 1) {
        switch(o->type) {
        case REDIS_STRING: freeStringObject(o); break;
        case REDIS_LIST: freeListObject(o); break;
        case REDIS_SET: freeSetObject(o); break;
        case REDIS_ZSET: freeZsetObject(o); break;
        case REDIS_HASH: freeHashObject(o); break;
        default: redisPanic("Unknown object type"); break;
        }
        zfree(o);

    // 減少計數
    } else {
        o->refcount--;
    }
}