跟着大彬讀源碼 - Redis 6 - 對象和數據類型(下)

[TOC]redis

繼續擼咱們的對象和數據類型。服務器

上節咱們一塊兒認識了字符串和列表,接下來還有哈希、集合和有序集合。數據結構

1 哈希對象

哈希對象的可選編碼分別是:ziplist 和 hashtable。app

1.1 ziplist 編碼的哈希對象

ziplist 編碼的哈希對象使用壓縮列表做爲底層實現。每當有新的鍵值對要加入到哈希對象時,程序會先將保存了的壓縮列表節點推入到表尾,而後再將保存了的壓縮列表節點推入到表尾。所以:函數

  • 保存了鍵值對的兩個節點老是緊挨在一塊兒,保存鍵的節點在前,保存值的節點在後;
  • 先添加到哈希對象中的鍵值對會被仿造壓縮列表的表頭方向,後添加的鍵值對會被放在壓縮列表的表尾方向。

執行如下 HSET 命令,服務器將建立一個如圖 9 所示的列表對象做爲 profile 鍵的值:ui

127.0.0.1:6379> HSET profile name "Tom"
(integer) 1
127.0.0.1:6379> HSET profile age "25"
(integer) 1
127.0.0.1:6379> HSET profile career "Programer"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING profile
"ziplist"

圖 9 - ziplist 編碼的哈希對象

其中對象所使用的壓縮列表如圖 10 所示:編碼

圖 10 - ziplist 編碼的哈希對象中壓縮列表結構

1.2 hashtable 編碼的

hashtable 編碼的哈希對象使用字典做爲底層實現。哈希對象中的每一個鍵值對都使用一個字典鍵值對來保存:3d

  • 字典中的每一個鍵都是一個字符串對象,對象中保存了鍵值對的鍵;
  • 字典中的每一個值都是一個字符串對象,對象中保存了鍵值對的值。

若是前面的 profile 鍵使用的是 hashtable 編碼的哈希對象,那麼這個哈希對象應該如圖 11 所示:指針

圖 11 - hashtable 編碼的哈希對象

1.3 編碼轉換

當哈希對象同時符合下面兩個條件時,將使用 ziplist 編碼:code

  1. 哈希對象保存的全部鍵值對中,鍵和值的字符串長度都小於 64 個字節;
  2. 哈希對象保存的鍵值對數量小於 512 個。

上述條件中的臨界值對應 redis.conf 文件中的配置:hash-max-ziplist-valuehash-max-ziplist-entries

在 3.2 版本中,新增一個哈希鍵值對時,實際上老是先建立一個 ziplist 編碼的哈希對象,而後再進行轉換檢查。 關於什麼時候進行編碼轉換,有兩種狀況發生:

  1. 更新或新增鍵值對時,若是值的字節數大於 hash-max-ziplist-value,將從 ziplist 編碼轉成 hashtable 編碼;
  2. 新增鍵值對時,若是哈希中的鍵值對數量大於 hash-max-ziplist-entries,將從 ziplist 編碼轉成 hashtable 編碼。

要注意的是,上述發生轉換的狀況,都不會出現從 hashtable 轉成 ziplist 的狀況,即便符合條件。

關於哈希編碼轉換的函數,能夠參考 t_hash.c/hashTypeConvert,源碼以下:

# o 是原始對象,enc 是目標編碼。
void hashTypeConvert(robj *o, int enc) {
    if (o->encoding == OBJ_ENCODING_ZIPLIST) { // 原始編碼是 OBJ_ENCODING_ZIPLIST 才進行轉換
        hashTypeConvertZiplist(o, enc);
    } else if (o->encoding == OBJ_ENCODING_HT) {
        serverPanic("Not implemented");
    } else {
        serverPanic("Unknown hash encoding");
    }
}

2 集合對象

集合對象的可選編碼有:intset 和 hashtable。

2.1 intset 編碼的集合對象

intset 編碼的集合對象使用整數集合做爲底層實現,集合對象包含的全部元素都被保存在整數集合裏面。

執行如下 SADD 命令,將建立一個如圖 12 所示的 intset 編碼的集合對象:

127.0.0.1:6379> SADD numbers 1 3 5
(integer) 3
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"

圖 12 - intset 編碼的集合對象

2.2 hashtable 編碼的集合對象

hashtable 編碼的集合對象使用字典做爲底層實現,字典的每一個鍵都是一個字符串對象,每一個字符串對象中又包含了一個集合元素,而字典的值則所有設置爲 NULL。

執行如下 SADD 命令,將建立一個如圖 13 所示的 hashtable 編碼的集合對象:

127.0.0.1:6379> SADD fruits "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> OBJECT ENCODING fruits
"hashtable"

圖 13 - hashtable 編碼的集合對象

2.3 編碼轉換

當集合對象同時知足如下兩個條件時,對象使用 intset 編碼:

  1. 集合對象保存的全部元素都是能夠被 long double 表示整數值;
  2. 集合對象保存的元素數量不超過 512 個。

上述條件中的臨界值對應 redis.conf 文件中的配置:set-max-intset-entries

對於集合對象,在新增第一個鍵值對時,就會對鍵值對中的值進行檢查,若是是符合條件的整數值,就會建立一個 intset 編碼的集合對象,不然,則建立 hashtable 編碼的集合對象。

關於什麼時候進行編碼轉換,有兩種狀況發生:

  1. 更新或新增鍵值對時,若是不能用 long double 表示,將從 intset 編碼轉成 hashtable 編碼;
  2. 新增鍵值對時,若是集合中的鍵值對數量大於 set-max-intset-entries,將從 intset 編碼轉成 hashtable 編碼。

一樣,上述發生轉換的狀況,都不會出現從 hashtable 轉成 intset 的狀況,即便符合條件。

關於哈希編碼轉換的函數,能夠參考 t_set.c/setTypeConvert,源碼以下:

# setobj 是原始對象,enc 是目標編碼。
hvoid setTypeConvert(robj *setobj, int enc) {
    setTypeIterator *si;
    serverAssertWithInfo(NULL,setobj,setobj->type == OBJ_SET && setobj->encoding == OBJ_ENCODING_INTSET);
    if (enc == OBJ_ENCODING_HT) { // 只能轉成 OBJ_ENCODING_HT 編碼
        int64_t intele;
        dict *d = dictCreate(&setDictType,NULL);
        robj *element;
        /* Presize the dict to avoid rehashing */
        dictExpand(d,intsetLen(setobj->ptr));
        /* To add the elements we extract integers and create redis objects */
        si = setTypeInitIterator(setobj);
        while (setTypeNext(si,&element,&intele) != -1) {
            element = createStringObjectFromLongLong(intele);
            serverAssertWithInfo(NULL,element,
                                dictAdd(d,element,NULL) == DICT_OK);
        }
        setTypeReleaseIterator(si);
        setobj->encoding = OBJ_ENCODING_HT;
        zfree(setobj->ptr);
        setobj->ptr = d;
    } else {
        serverPanic("Unsupported set conversion");
    }
}

3 有序集合對象

有序集合對象的可選編碼有:ziplist 和 skiplist。

3.1 ziplist 編碼的有序集合對象

intset 編碼的集合對象使用壓縮列表做爲底層實現。每一個集合元素使用兩個緊挨在一塊兒的壓縮列表節點來保存。第一個節點保存元素的成員(member),第二個成員保存元素的分值(score)。

壓縮列表內的集合元素按分值從小到大排序,分值較小的元素被放置在表頭的方向,而分值較大的元素則被放置在靠近表尾的方向。

執行如下 SADD 命令,將建立一個如圖 14 所示的 ziplist 編碼的集合對象:

127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> OBJECT ENCODING price
"ziplist"

圖 14 - ziplist 編碼的有序集合對象

底層結構 ziplist 如圖 15 所示: 圖 15 - ziplist 編碼的有序集合,數據在壓縮列表中按分值從小到大排列

3.2 skiplist 編碼的集合對象

skiplist 編碼的集合對象使用 zset 做爲底層實現。一個 zset 結構同時包含一個字典和一個跳躍表。結構源碼以下:

# server.h
typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

zset 結構中的 zsl 跳躍表按分值從小到大保存了全部集合元素,每一個跳躍表節點都保存了一個集合元素。

跳躍表節點的 object 屬性保存了元素的成員,而跳躍表節點的 score 屬性則保存了元素的分支。**程序經過這個跳躍表,對有序集合進行範圍型操做。好比 ZRANK、ZRANGE 等命令就是基於跳躍表 API 來實現的。

除此以外,zset 結構中的 dict 字典爲有序集合建立了一個從成員到分值的映射。字典中的每一個鍵值對都保存了一個集合元素:字典中的鍵保存了元素的成員,而字典的值則保存了元素的分值。經過這個字典,程序用 O(1) 複雜度查找給定成員的分值。

有序集合每一個元素的成員都是一個字符串對象,而每一個元素的分值都是一個 double 類型的浮點數。值得一提的是,雖然 zset 結構同時使用跳躍表和字典保存了有序集合的元素,但這兩種數據結構都會經過指針來共享相同元素的成員和分值,因此不會產生任何重複成員和分值,也不會所以而浪費額外的內存。

若是前面 price 鍵建立的不是 ziplist 編碼的有序集合對象,而是 skiplist 編碼,那麼這個有序集合對象將會如圖 16 所示,而對象所使用的 zset 結果將會如圖 17 所示: 圖 16 - skiplist 編碼的有序集合對象

圖 17 - 有序集合元素同時被存放在字典和跳躍表中

圖 17 中,爲了展現方便,重複展現了各個元素的成員和分值。實際上,它們是共享元素的成員和分值。

3.3 編碼轉換

當有序集合對象同時知足如下兩個條件時,對象使用 ziplist 編碼:

  1. 有序集合對象保存的元素數量不超過 128 個。
  2. 有序集合中保存的全部元素成員的長度都小於 64 個字節。

上述條件中的臨界值對應 redis.conf 文件中的配置:zset-max-ziplist-entrieszset-max-ziplist-value

對於集合對象,在新增鍵值對時,就會對集合元素以及鍵值對中的值進行檢查,若是是符合條件,就會建立一個 ziplist 編碼的集合對象,不然,則建立 skiplist 編碼的集合對象。對應源碼以下:

# t_zset.c/zaddGenericCommand
...
zobj = lookupKeyWrite(c->db,key);
if (zobj == NULL) {
    if (xx) goto reply_to_client; /* No key + XX option: nothing to do. */
    if (server.zset_max_ziplist_entries == 0 ||
        server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
    {
        # 對象元素數量爲 0,或者
        zobj = createZsetObject();
    } else {
        zobj = createZsetZiplistObject();
    }
    dbAdd(c->db,key,zobj);
} else {
    if (zobj->type != OBJ_ZSET) {
        addReply(c,shared.wrongtypeerr);
        goto cleanup;
    }
}

總結

  1. 哈希對象有 ziplisthashtable 編碼。
  2. 集合對象有 intsethashtable 編碼。
  3. 有序集合對象有 ziplistskiplist 編碼。
相關文章
相關標籤/搜索