深刻了解Redis【二】對象及數據結構綜述

--html

引言

Redis中每一個鍵值對都是由對象組成:java

  • 鍵老是一個字符串對象(string)
  • 值能夠是字符串對象(string)、列表對象(list)、哈希對象(hash)、集合對象(set)、有序集合對象(zset)。

介紹

redis官方網站中對其數據類型的簡單介紹: An introduction to Redis data types and abstractions 摘抄一段關於redis key的介紹:redis

Redis keys Redis keys are binary safe, this means that you can use any binary sequence as a key, from a string like "foo" to the content of a JPEG file. The empty string is also a valid key. A few other rules about keys:數組

  • Very long keys are not a good idea. For instance a key of 1024 bytes is a bad idea not only memory-wise, but also because the lookup of the key in the dataset may require several costly key-comparisons. Even when the task at hand is to match the existence of a large value, hashing it (for example with SHA1) is a better idea, especially from the perspective of memory and bandwidth.
  • Very short keys are often not a good idea. There is little point in writing "u1000flw" as a key if you can instead write "user:1000:followers". The latter is more readable and the added space is minor compared to the space used by the key object itself and the value object. While short keys will obviously consume a bit less memory, your job is to find the right balance.
  • Try to stick with a schema. For instance "object-type:id" is a good idea, as in "user:1000". Dots or dashes are often used for multi-word fields, as in "comment:1234:reply.to" or "comment:1234:reply-to".
  • The maximum allowed key size is 512 MB.

源碼中的對象數據結構

在redis源碼中,對象的數據結構定義在redis.h文件中數據結構

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

下面分別介紹如下對象中定義的屬性定義:less

  • type type是指對象的5中基本類型:string、hash、list、set、zset。
// 對象類型
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
  • encoding 表示對象的底層編碼實現,有簡單動態字符串、鏈表、字典、跳躍表、整數集合、壓縮列表。
// 對象編碼
#define REDIS_ENCODING_RAW 0     /* Raw representation */
#define REDIS_ENCODING_INT 1     /* Encoded as integer */
#define REDIS_ENCODING_HT 2      /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
  • lru 最後一次訪問對象的時間,用來處理鍵的過時策略。
  • refcount 對象引用的指針,不一樣key的相同對象引用同一個對象,減小內存分配即對象共享。也用於內存釋放。
  • *ptr 指向底層數據結構的指針。

基本類型type與底層數據結構的對應關係

盜取一張圖(懶的畫圖): 基本類型與底層數據結構的對應關係.jpg 各類對象的建立能夠參考redis源碼中的object.c文件,如: 根據傳入的整數型,建立一個字符串:ide

/*
 * 根據傳入的整數值,建立一個字符串對象
 *
 * 這個字符串的對象保存的能夠是 INT 編碼的 long 值,
 * 也能夠是 RAW 編碼的、被轉換成字符串的 long long 值。
 */
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;
}

線面簡單看看基本類型type對應的底層數據結構:函數

string

sting在redis底層對應三種編碼方式,兩種數據結構。 若是一個字符串內容能夠轉成long,那麼編碼方式爲int,底層數據結構爲int. 若是普通字符串對象的長度小於39字節,就用embstr對象。不然用的raw對象,底層數據結構爲簡單動態字符串。post

/* 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. */
#define REDIS_ENCODING_EMBSTR_SIZE_LIMIT 39
robj *createStringObject(char *ptr, size_t len) {
    if (len <= REDIS_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}
SDS

簡單動態字符串(simple dynamic string)的數據結構爲:網站

/*
 * 保存字符串對象的結構
 */
struct sdshdr {
    // buf 中已佔用空間的長度
    int len;
    // buf 中剩餘可用空間的長度
    int free;
    // 數據空間
    char buf[];
};

list

有兩種編碼實現,鏈表linkedlist和壓縮列表ziplist,噹噹list元素少且元素內容長度不大時,使用ziplist,不然使用linkedlist.

/* Use a real list when there are too many entries 
         *
         * 根據節點數,建立對象的編碼
         */
        if (len > server.list_max_ziplist_entries) {
            o = createListObject();
        } else {
            o = createZiplistObject();
        }
/*
 * 建立一個 LINKEDLIST 編碼的列表對象
 */
robj *createListObject(void) {

    list *l = listCreate();

    robj *o = createObject(REDIS_LIST,l);

    listSetFreeMethod(l,decrRefCountVoid);

    o->encoding = REDIS_ENCODING_LINKEDLIST;

    return o;
}

/*
 * 建立一個 ZIPLIST 編碼的列表對象
 */
robj *createZiplistObject(void) {

    unsigned char *zl = ziplistNew();

    robj *o = createObject(REDIS_LIST,zl);

    o->encoding = REDIS_ENCODING_ZIPLIST;

    return o;
}

#####鏈表linkedlist 其底層數據結構list位於adlist.h中:

/*
 * 雙端鏈表節點
 */
typedef struct listNode {
    // 前置節點
    struct listNode *prev;
    // 後置節點
    struct listNode *next;
    // 節點的值
    void *value;
} listNode;

/*
 * 雙端鏈表結構
 */
typedef struct list {
    // 表頭節點
    listNode *head;
    // 表尾節點
    listNode *tail;
    // 節點值複製函數
    void *(*dup)(void *ptr);
    // 節點值釋放函數
    void (*free)(void *ptr);
    // 節點值對比函數
    int (*match)(void *ptr, void *key);
    // 鏈表所包含的節點數量
    unsigned long len;
} list;
壓縮列表ziplist

相似數組,可是每一個節點存儲的數據大小不一樣,節點上有length屬性。

hash

有兩種數據結構實現,壓縮列表ziplist和字典dict.

字典dict

至關於java中的HashMap。解決hash衝突使用的是鏈表法,好像沒有上升到紅黑樹。

set

若是是整數類型,直接使用整數集合intset,若是不是,就用字典,和java的set同樣。

zset

有序的set,元素個數少且不大,就用壓縮列表ziplist,不然就用跳躍表skiplist.

跳躍表skiplist

跳躍表(skiplist)是一種有序數據結構,它經過在每一個節點中維持多個指向其餘節點的指針,從而達到快速訪問節點的目的。 跳躍表是一種隨機化的數據,跳躍表以有序的方式在層次化的鏈表中保存元素。 定義位於redis.h中。

/* ZSETs use a specialized version of Skiplists */
/*
 * 跳躍表節點
 */
typedef struct zskiplistNode {

    // 成員對象
    robj *obj;

    // 分值
    double score;

    // 後退指針
    struct zskiplistNode *backward;

    // 層
    struct zskiplistLevel {

        // 前進指針
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode;

/*
 * 跳躍表
 */
typedef struct zskiplist {

    // 表頭節點和表尾節點
    struct zskiplistNode *header, *tail;

    // 表中節點的數量
    unsigned long length;

    // 表中層數最大的節點的層數
    int level;

} zskiplist;

參考文檔

深刻淺出Redis-redis底層數據結構(上) 深刻淺出Redis-redis底層數據結構(下) Redis基本類型及其數據結構 Redis的五種對象類型及其底層實現 Redis-基本數據類型與內部存儲結構 redis的五種基本數據類型及其內部實現 《Redis設計與實現》黃健宏

tencent.jpg

相關文章
相關標籤/搜索