Redis源碼學習-5-對象

對象


幾個概念澄清。Redis並不直接使用之前學習的數據結構,而是使用對象

1. 對象分類

5大對象

類型常量 對象的名稱
REDIS_STRING 字符串對象
REDIS_LIST 列表對象
REDIS_HASH 哈希對象
REDIS_SET 集合對象
REDIS_ZSET 有序集合對象

Redis的數據存儲總是分爲key-value的形式。而key和value分別是以對象存儲的。其中對於key來說,永遠是字符串對象。而值則是以上任意。

因此只要是在Redis中談到的對象類型時候,都是在談值的對象類型。因爲key的對象是固定死了的。

2. 數據結構

typedef struct redisObject{
// 類型
unsigned type:4;
// 編碼
unsigned encoding:4;
// 對象最後一次被訪問的時間
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
// 引用計數
int refcount;
// data
void *ptr;

2.1 類型

類型即指對象類型

2.2 編碼

編碼指的是底層數據結構的實現。對於同一種對象,可能會有不同的底層數據結構實現。

目的是爲了增加靈活性和效率。

3. 接下來的目標

到目前爲止,我們已經完成了Redis底層數據結構的閱讀。接下來我們就需要進行編碼部分的閱讀。看看Redis是如何利用這些底層的數據結構來具體實現這5種對象的。

5. 各種對象

5.1 創建一個新 robj 對象

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

我們可以看到,在創建一個robj對象時

  1. 設置了引用計數
  2. 設置了時鐘,後期在淘汰key對象的時候會被用到。

接下來在各種對象的創建時,可能會用到這裏。

5.2 字符串對象

5.2.1 編碼

字符串對象的編碼可以是

  1. REDIS_ENCODING_INT
  2. REDIS_ENCODING_EMBSTR
  3. REDIS_ENCODING_RAW

分別對應如下命令

redis> SET number 10086

redis> SET sds "string"

redis> SET story "long,long,long ago, there lived a king ... this is a very long string larger than 39 bytes"

對應規則如下。

  1. 當字符串對象的值可以用long類型來表示。那麼void*就可以用來被long存儲。此時編碼設置爲INT
  2. 當字符串長度大於39字節的時候,就會被設置爲RAW。

根據《Redis的設計與實現》談到,embstr和raw的區別時。
相同點

  • embstr和raw的數據結構時一模一樣的。

不同點

  • embstr是一次分配內存,因此刪除也是一次。而raw需要2次。
  • 因爲使一次分配內存,所以embstr的內存分佈是連續的。

5.2.2 embstr的創建

let’s show code

robj *createEmbeddedStringObject(char *ptr, size_t len) {
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
    // 1次分配內存, len+1爲sds.buff長度和結束符的存放
    struct sdshdr *sh = (void*)(o+1);
    // o是robj類型,因此o+1,移動sizeof(robj)個字節

    o->type = REDIS_STRING;
    o->encoding = REDIS_ENCODING_EMBSTR;
    o->ptr = sh+1;
    // sh移動sizeof(sds)字節,指向buff,這樣纔可以使用SDS API
    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;
}

這裏需要對指針的+1理解充分,且需要理解底層數據結構。

簡單例子

int *a;
struct B *b;
char *c;

//a+1;
//b+1;
//c+1;

int aMove = reinterpret_cast<char*>(a+1) - reinterpret_cast<char*>(a);
int bMove = reinterpret_cast<char*>(b+1) - reinterpret_cast<char*>(b);
int cMove = reinterpret_cast<char*>(c+1) - reinterpret_cast<char*>(c);

這裏a會移動4個字節,b移動sizeof(b)個字節,c移動1個字節。

5.3 列表對象

5.3.1 編碼

列表對象的編碼使用ziplist或者linkedlist

比如使用

redis>RPUSH numbers 1 "three" 5

ziplist的底層實現就是簡單地將這些轉爲ziplistNode然後存放。

而對於linkedlist來說,則全部存放爲字符串對象,這實際上是一種對象中嵌套對象的做法。

5.4 哈希對象

5.4.1 編碼

哈希對象的編碼可以是ziplist或者hashtable

對於ziplist來說,實際上就是簡單的將鍵值對放在一起,然後ziplist實際上像是一個連續空間的鏈表。顯然,只有在元素很少的時候,才能使用。

5.5 集合對象

5.5.1 編碼

對於無序的集合對象來說,使用的是intset和hashtable。

我們重點來看有序的集合對象,其編碼使用的是ziplist和skiplist。

5.5.2 zset

爲什麼有序集合zset中既使用跳錶,又使用哈希表來做數據結構?

zset雖然是有序集合,實際上也可以看做是value = score的鍵值對。

  1. 想要哈希表的O(1)根據成員名獲得分值的特性。
  2. 同時哈希表的底層是無序的,不能通過使用ZRANK來獲得指定範圍內的元素。如果只有哈希表,就需要通過排序來得到其有序結構O(nlogN),而且隨着新插入元素,還需要重新排序。所以不如在底層就維護好一個有序結構。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章