Redis設計與實現 第一部分

1、數據結構與實現

1.簡單動態字符串

在C語言中,字符串是以字符數組的形式體現的(以’\0’爲結束符),html

Redis中的字符串定義以下:node

/* 字符串結構體(字符串就是字符數組) */
struct sdshdr {
    // 字符串當前長度
    unsigned int len;
    // 剩餘可用長度
    unsigned int free;
    // 字符數組(具體存放字符串的地方)
    char buf[];
};
  • Redis 只會使用 C 字符串做爲字面量, 在大多數狀況下, Redis 使用 SDS (Simple Dynamic String,簡單動態字符串)做爲字符串表示。
  • 比起 C 字符串, SDS 具備如下優勢:
    1. 常數複雜度獲取字符串長度。
    2. 杜絕緩衝區溢出。
    3. 減小修改字符串長度時所需的內存重分配次數。
    4. 二進制安全。
    5. 兼容部分 C 字符串函數。

2.鏈表

typedef struct list {
    // 表頭節點
    listNode *head;
    // 表尾節點
    listNode *tail;
    // 鏈表所包含的節點數量
    unsigned long len;
    // 節點值複製函數
    void *(*dup)(void *ptr);
    // 節點值釋放函數
    void (*free)(void *ptr);
    // 節點值對比函數
    int (*match)(void *ptr, void *key);
} list;
  • 鏈表被普遍用於實現 Redis 的各類功能, 好比列表鍵, 發佈與訂閱, 慢查詢, 監視器, 等等。
  • 每一個鏈表節點由一個listnode 結構來表示, 每一個節點都有一個指向前置節點和後置節點的指針, 因此 Redis 的鏈表實現是雙端鏈表。
  • 每一個鏈表使用一個list結構來表示, 這個結構帶有表頭節點指針、表尾節點指針、以及鏈表長度等信息。
  • 由於鏈表表頭節點的前置節點和表尾節點的後置節點都指向NULL, 因此 Redis 的鏈表實現是無環鏈表。
  • 經過爲鏈表設置不一樣的類型特定函數, Redis 的鏈表能夠用於保存各類不一樣類型的值。

3. 字典

哈希表redis

typedef struct dictht {
    // 哈希表數組
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    // 哈希表大小掩碼,用於計算索引值
    // 老是等於 size - 1
    unsigned long sizemask;
    // 該哈希表已有節點的數量
    unsigned long used;
} dictht;

哈希表節點 算法

typedef struct dictEntry {
    // 鍵
    void *key;
    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;
    // 指向下個哈希表節點,造成鏈表
    struct dictEntry *next;
} dictEntry;

字典數據庫

typedef struct dict {
    // 類型特定函數
    dictType *type;
    // 私有數據
    void *privdata;
    // 哈希表
    dictht ht[2];
    // rehash 索引
    // 當 rehash 不在進行時,值爲 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
} dict;

dictType數組

typedef struct dictType {
    // 計算哈希值的函數
    unsigned int (*hashFunction)(const void *key);
    // 複製鍵的函數
    void *(*keyDup)(void *privdata, const void *key);
    // 複製值的函數
    void *(*valDup)(void *privdata, const void *obj);
    // 對比鍵的函數
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    // 銷燬鍵的函數
    void (*keyDestructor)(void *privdata, void *key);
    // 銷燬值的函數
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

當如下條件中的任意一個被知足時, 程序會自動開始對哈希表執行擴展操做:安全

# 負載因子 = 哈希表已保存節點數量 / 哈希表大小
load_factor = ht[0].used / ht[0].size
  1. 服務器目前沒有在執行 BGSAVE 命令或者 BGREWRITEAOF 命令, 而且哈希表的負載因子大於等於 1
  2. 服務器目前正在執行 BGSAVE 命令或者 BGREWRITEAOF 命令, 而且哈希表的負載因子大於等於 5
  3. 當哈希表的負載因子小於 0.1 時, 程序自動開始對哈希表執行收縮操做。

重點回顧服務器

  1. 字典被普遍用於實現 Redis 的各類功能, 其中包括數據庫和哈希鍵。
  2. Redis 中的字典使用哈希表做爲底層實現, 每一個字典帶有兩個哈希表, 一個用於平時使用, 另外一個僅在進行 rehash 時使用。
  3. 當字典被用做數據庫的底層實現, 或者哈希鍵的底層實現時, Redis 使用 MurmurHash2 算法來計算鍵的哈希值。
  4. 哈希表使用鏈地址法來解決鍵衝突, 被分配到同一個索引上的多個鍵值對會鏈接成一個單向鏈表。
  5. 在對哈希表進行擴展或者收縮操做時, 程序須要將現有哈希表包含的全部鍵值對 rehash 到新哈希表裏面, 而且這個 rehash 過程並非一次性地完成的, 而是漸進式地完成的。

4.跳躍表

跳躍表節點的實現由 redis.h/zskiplistNode 結構定義:數據結構

typedef struct zskiplistNode {
    // 後退指針
    struct zskiplistNode *backward;
    // 分值
    double score;
    // 成員對象
    robj *obj;
    // 層
    struct zskiplistLevel {
        // 前進指針
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;

zskiplist 結構的定義以下:dom

typedef struct zskiplist {
    // 表頭節點和表尾節點
    struct zskiplistNode *header, *tail;
    // 表中節點的數量
    unsigned long length;
    // 表中層數最大的節點的層數
    int level;
} zskiplist;

header 和 tail 指針分別指向跳躍表的表頭和表尾節點, 經過這兩個指針, 程序定位表頭節點和表尾節點的複雜度爲 O(1) 。

經過使用 length 屬性來記錄節點的數量, 程序能夠在 O(1) 複雜度內返回跳躍表的長度。

level 屬性則用於在 O(1) 複雜度內獲取跳躍表中層高最大的那個節點的層數量, 注意表頭節點的層高並不計算在內。

重點回顧

  • 跳躍表是有序集合的底層實現之一, 除此以外它在 Redis 中沒有其餘應用。
  • Redis 的跳躍表實現由 zskiplist 和 zskiplistNode 兩個結構組成, 其中 zskiplist 用於保存跳躍表信息(好比表頭節點、表尾節點、長度), 而 zskiplistNode 則用於表示跳躍表節點。
  • 每一個跳躍表節點的層高都是 1 至 32 之間的隨機數。
  • 在同一個跳躍表中, 多個節點能夠包含相同的分值, 但每一個節點的成員對象必須是惟一的。
  • 跳躍表中的節點按照分值大小進行排序, 當分值相同時, 節點按照成員對象的大小進行排序。

5.整數集合

整數集合(intset)是 Redis 用於保存整數值的集合抽象數據結構, 它能夠保存類型爲 int16_t 、 int32_t 或者 int64_t 的整數值, 而且保證集合中不會出現重複元素

typedef struct intset {
    // 編碼方式
    uint32_t encoding;
    // 集合包含的元素數量
    uint32_t length;
    // 保存元素的數組
    int8_t contents[];
} intset;

重點回顧

  • 整數集合是集合鍵的底層實現之一。
  • 整數集合的底層實現爲數組, 這個數組以有序、無重複的方式保存集合元素, 在有須要時, 程序會根據新添加元素的類型, 改變這個數組的類型。
  • 升級操做爲整數集合帶來了操做上的靈活性, 而且儘量地節約了內存。
  • 整數集合只支持升級操做, 不支持降級操做。

6.壓縮列表

digraph {

    label = "\n 圖 7-1    壓縮列表的各個組成部分";

    node [shape = record];

    ziplist [label = " zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend "];

}

壓縮列表各個組成部分的詳細說明

屬性 類型 長度 用途
zlbytes uint32_t 4 字節 記錄整個壓縮列表佔用的內存字節數:在對壓縮列表進行內存重分配, 或者計算 zlend的位置時使用。
zltail uint32_t 4 字節 記錄壓縮列表表尾節點距離壓縮列表的起始地址有多少字節: 經過這個偏移量,程序無須遍歷整個壓縮列表就能夠肯定表尾節點的地址。
zllen uint16_t 2 字節 記錄了壓縮列表包含的節點數量: 當這個屬性的值小於 UINT16_MAX (65535)時, 這個屬性的值就是壓縮列表包含節點的數量; 當這個值等於 UINT16_MAX 時, 節點的真實數量須要遍歷整個壓縮列表才能計算得出。
entryX 列表節點 不定 壓縮列表包含的各個節點,節點的長度由節點保存的內容決定。
zlend uint8_t 1 字節 特殊值 0xFF (十進制 255 ),用於標記壓縮列表的末端。

每一個壓縮列表節點都由 previous_entry_length 、 encoding 、 content 三個部分組成, 如圖 :

digraph {

    label = "\n 圖 7-4    壓縮列表節點的各個組成部分";

    node [shape = record];

    n [label = " previous_entry_length | encoding | content "];

}

 每一個節點的 previous_entry_length 屬性都記錄了前一個節點的長度:

  • 若是前一節點的長度小於 254 字節, 那麼 previous_entry_length 屬性須要用 1 字節長的空間來保存這個長度值。
  • 若是前一節點的長度大於等於 254 字節, 那麼 previous_entry_length 屬性須要用 5 字節長的空間來保存這個長度值。

重點回顧

  • 壓縮列表是一種爲節約內存而開發的順序型數據結構。
  • 壓縮列表被用做列表鍵和哈希鍵的底層實現之一。
  • 壓縮列表能夠包含多個節點,每一個節點能夠保存一個字節數組或者整數值。
  • 添加新節點到壓縮列表, 或者從壓縮列表中刪除節點, 可能會引起連鎖更新操做, 但這種操做出現的概率並不高。

7.對象

readobject

typedef struct redisObject {
    // 類型
    unsigned type:4;
    // 編碼
    unsigned encoding:4;
    // 指向底層實現數據結構的指針
    void *ptr;
    // ...
} robj;

對象的編碼

編碼常量 編碼所對應的底層數據結構
REDIS_ENCODING_INT long 類型的整數
REDIS_ENCODING_EMBSTR embstr 編碼的簡單動態字符串
REDIS_ENCODING_RAW 簡單動態字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 雙端鏈表
REDIS_ENCODING_ZIPLIST 壓縮列表
REDIS_ENCODING_INTSET 整數集合
REDIS_ENCODING_SKIPLIST 跳躍表和字典

不一樣類型和編碼的對象

類型 編碼 對象
REDIS_STRING REDIS_ENCODING_INT 使用整數值實現的字符串對象。
REDIS_STRING REDIS_ENCODING_EMBSTR 使用 embstr 編碼的簡單動態字符串實現的字符串對象。
REDIS_STRING REDIS_ENCODING_RAW 使用簡單動態字符串實現的字符串對象。
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用壓縮列表實現的列表對象。
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用雙端鏈表實現的列表對象。
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用壓縮列表實現的哈希對象。
REDIS_HASH REDIS_ENCODING_HT 使用字典實現的哈希對象。
REDIS_SET REDIS_ENCODING_INTSET 使用整數集合實現的集合對象。
REDIS_SET REDIS_ENCODING_HT 使用字典實現的集合對象。
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用壓縮列表實現的有序集合對象。
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳躍表和字典實現的有序集合對象。

 OBJECT ENCODING 對不一樣編碼的輸出

對象所使用的底層數據結構 編碼常量 OBJECT ENCODING 命令輸出
整數 REDIS_ENCODING_INT "int"
embstr 編碼的簡單動態字符串(SDS) REDIS_ENCODING_EMBSTR "embstr"
簡單動態字符串 REDIS_ENCODING_RAW "raw"
字典 REDIS_ENCODING_HT "hashtable"
雙端鏈表 REDIS_ENCODING_LINKEDLIST "linkedlist"
壓縮列表 REDIS_ENCODING_ZIPLIST "ziplist"
整數集合 REDIS_ENCODING_INTSET "intset"
跳躍表和字典 REDIS_ENCODING_SKIPLIST "skiplist"

(1)字符串對象

字符串對象的編碼能夠是 int 、 raw 或者 embstr (特殊,短string,不能修改)

命令 int 編碼的實現方法 embstr 編碼的實現方法 raw 編碼的實現方法
SET 使用 int 編碼保存值。 使用 embstr 編碼保存值。 使用 raw 編碼保存值。
GET 拷貝對象所保存的整數值, 將這個拷貝轉換成字符串值, 而後向客戶端返回這個字符串值。 直接向客戶端返回字符串值。 直接向客戶端返回字符串值。
APPEND 將對象轉換成 raw 編碼, 而後按 raw編碼的方式執行此操做。 將對象轉換成 raw 編碼, 而後按 raw編碼的方式執行此操做。 調用 sdscatlen 函數, 將給定字符串追加到現有字符串的末尾。
INCRBYFLOAT 取出整數值並將其轉換成 longdouble 類型的浮點數, 對這個浮點數進行加法計算, 而後將得出的浮點數結果保存起來。 取出字符串值並嘗試將其轉換成long double 類型的浮點數, 對這個浮點數進行加法計算, 而後將得出的浮點數結果保存起來。 若是字符串值不能被轉換成浮點數, 那麼向客戶端返回一個錯誤。 取出字符串值並嘗試將其轉換成 longdouble 類型的浮點數, 對這個浮點數進行加法計算, 而後將得出的浮點數結果保存起來。 若是字符串值不能被轉換成浮點數, 那麼向客戶端返回一個錯誤。
INCRBY 對整數值進行加法計算, 得出的計算結果會做爲整數被保存起來。 embstr 編碼不能執行此命令, 向客戶端返回一個錯誤。 raw 編碼不能執行此命令, 向客戶端返回一個錯誤。
DECRBY 對整數值進行減法計算, 得出的計算結果會做爲整數被保存起來。 embstr 編碼不能執行此命令, 向客戶端返回一個錯誤。 raw 編碼不能執行此命令, 向客戶端返回一個錯誤。
STRLEN 拷貝對象所保存的整數值, 將這個拷貝轉換成字符串值, 計算並返回這個字符串值的長度。 調用 sdslen 函數, 返回字符串的長度。 調用 sdslen 函數, 返回字符串的長度。
SETRANGE 將對象轉換成 raw 編碼, 而後按 raw編碼的方式執行此命令。 將對象轉換成 raw 編碼, 而後按 raw編碼的方式執行此命令。 將字符串特定索引上的值設置爲給定的字符。
GETRANGE 拷貝對象所保存的整數值, 將這個拷貝轉換成字符串值, 而後取出並返回字符串指定索引上的字符。 直接取出並返回字符串指定索引上的字符。 直接取出並返回字符串指定索引上的字符。

(2)列表對象

列表對象的編碼能夠是 ziplist 或者 linkedlis

列表命令的實現

命令 ziplist 編碼的實現方法 linkedlist 編碼的實現方法
LPUSH 調用 ziplistPush 函數, 將新元素推入到壓縮列表的表頭。 調用 listAddNodeHead 函數, 將新元素推入到雙端鏈表的表頭。
RPUSH 調用 ziplistPush 函數, 將新元素推入到壓縮列表的表尾。 調用 listAddNodeTail 函數, 將新元素推入到雙端鏈表的表尾。
LPOP 調用 ziplistIndex 函數定位壓縮列表的表頭節點, 在向用戶返回節點所保存的元素以後, 調用 ziplistDelete 函數刪除表頭節點。 調用 listFirst 函數定位雙端鏈表的表頭節點, 在向用戶返回節點所保存的元素以後, 調用 listDelNode 函數刪除表頭節點。
RPOP 調用 ziplistIndex 函數定位壓縮列表的表尾節點, 在向用戶返回節點所保存的元素以後, 調用 ziplistDelete 函數刪除表尾節點。 調用 listLast 函數定位雙端鏈表的表尾節點, 在向用戶返回節點所保存的元素以後, 調用 listDelNode 函數刪除表尾節點。
LINDEX 調用 ziplistIndex 函數定位壓縮列表中的指定節點, 而後返回節點所保存的元素。 調用 listIndex 函數定位雙端鏈表中的指定節點, 而後返回節點所保存的元素。
LLEN 調用 ziplistLen 函數返回壓縮列表的長度。 調用 listLength 函數返回雙端鏈表的長度。
LINSERT 插入新節點到壓縮列表的表頭或者表尾時, 使用 ziplistPush 函數; 插入新節點到壓縮列表的其餘位置時, 使用 ziplistInsert 函數。 調用 listInsertNode 函數, 將新節點插入到雙端鏈表的指定位置。
LREM 遍歷壓縮列表節點, 並調用 ziplistDelete 函數刪除包含了給定元素的節點。 遍歷雙端鏈表節點, 並調用 listDelNode 函數刪除包含了給定元素的節點。
LTRIM 調用 ziplistDeleteRange 函數, 刪除壓縮列表中全部不在指定索引範圍內的節點。 遍歷雙端鏈表節點, 並調用 listDelNode 函數刪除鏈表中全部不在指定索引範圍內的節點。
LSET 調用 ziplistDelete 函數, 先刪除壓縮列表指定索引上的現有節點, 而後調用 ziplistInsert 函數, 將一個包含給定元素的新節點插入到相同索引上面。 調用 listIndex 函數, 定位到雙端鏈表指定索引上的節點, 而後經過賦值操做更新節點的值

(3)哈希對象

哈希命令的實現

命令 ziplist 編碼實現方法 hashtable 編碼的實現方法
HSET 首先調用 ziplistPush 函數, 將鍵推入到壓縮列表的表尾, 而後再次調用 ziplistPush 函數, 將值推入到壓縮列表的表尾。 調用 dictAdd 函數, 將新節點添加到字典裏面。
HGET 首先調用 ziplistFind 函數, 在壓縮列表中查找指定鍵所對應的節點, 而後調用 ziplistNext 函數, 將指針移動到鍵節點旁邊的值節點, 最後返回值節點。 調用 dictFind 函數, 在字典中查找給定鍵, 而後調用 dictGetVal 函數, 返回該鍵所對應的值。
HEXISTS 調用 ziplistFind 函數, 在壓縮列表中查找指定鍵所對應的節點, 若是找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。 調用 dictFind 函數, 在字典中查找給定鍵, 若是找到的話說明鍵值對存在, 沒找到的話就說明鍵值對不存在。
HDEL 調用 ziplistFind 函數, 在壓縮列表中查找指定鍵所對應的節點, 而後將相應的鍵節點、 以及鍵節點旁邊的值節點都刪除掉。 調用 dictDelete 函數, 將指定鍵所對應的鍵值對從字典中刪除掉。
HLEN 調用 ziplistLen 函數, 取得壓縮列表包含節點的總數量, 將這個數量除以 2 , 得出的結果就是壓縮列表保存的鍵值對的數量。 調用 dictSize 函數, 返回字典包含的鍵值對數量, 這個數量就是哈希對象包含的鍵值對數量。
HGETALL 遍歷整個壓縮列表, 用 ziplistGet 函數返回全部鍵和值(都是節點)。 遍歷整個字典, 用 dictGetKey 函數返回字典的鍵, 用 dictGetVal 函數返回字典的值。

(4)集合對象

集合命令的實現方法

命令 intset 編碼的實現方法 hashtable 編碼的實現方法
SADD 調用 intsetAdd 函數, 將全部新元素添加到整數集合裏面。 調用 dictAdd , 以新元素爲鍵, NULL 爲值, 將鍵值對添加到字典裏面。
SCARD 調用 intsetLen 函數, 返回整數集合所包含的元素數量, 這個數量就是集合對象所包含的元素數量。 調用 dictSize 函數, 返回字典所包含的鍵值對數量, 這個數量就是集合對象所包含的元素數量。
SISMEMBER 調用 intsetFind 函數, 在整數集合中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。 調用 dictFind 函數, 在字典的鍵中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。
SMEMBERS 遍歷整個整數集合, 使用 intsetGet 函數返回集合元素。 遍歷整個字典, 使用 dictGetKey 函數返回字典的鍵做爲集合元素。
SRANDMEMBER 調用 intsetRandom 函數, 從整數集合中隨機返回一個元素。 調用 dictGetRandomKey 函數, 從字典中隨機返回一個字典鍵。
SPOP 調用 intsetRandom 函數, 從整數集合中隨機取出一個元素, 在將這個隨機元素返回給客戶端以後, 調用 intsetRemove 函數, 將隨機元素從整數集合中刪除掉。 調用 dictGetRandomKey 函數, 從字典中隨機取出一個字典鍵, 在將這個隨機字典鍵的值返回給客戶端以後, 調用 dictDelete 函數, 從字典中刪除隨機字典鍵所對應的鍵值對。
SREM 調用 intsetRemove 函數, 從整數集合中刪除全部給定的元素。 調用 dictDelete 函數, 從字典中刪除全部鍵爲給定元素的鍵值對。

(4)集合對象

集合命令的實現方法

命令 intset 編碼的實現方法 hashtable 編碼的實現方法
SADD 調用 intsetAdd 函數, 將全部新元素添加到整數集合裏面。 調用 dictAdd , 以新元素爲鍵, NULL 爲值, 將鍵值對添加到字典裏面。
SCARD 調用 intsetLen 函數, 返回整數集合所包含的元素數量, 這個數量就是集合對象所包含的元素數量。 調用 dictSize 函數, 返回字典所包含的鍵值對數量, 這個數量就是集合對象所包含的元素數量。
SISMEMBER 調用 intsetFind 函數, 在整數集合中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。 調用 dictFind 函數, 在字典的鍵中查找給定的元素, 若是找到了說明元素存在於集合, 沒找到則說明元素不存在於集合。
SMEMBERS 遍歷整個整數集合, 使用 intsetGet 函數返回集合元素。 遍歷整個字典, 使用 dictGetKey 函數返回字典的鍵做爲集合元素。
SRANDMEMBER 調用 intsetRandom 函數, 從整數集合中隨機返回一個元素。 調用 dictGetRandomKey 函數, 從字典中隨機返回一個字典鍵。
SPOP 調用 intsetRandom 函數, 從整數集合中隨機取出一個元素, 在將這個隨機元素返回給客戶端以後, 調用 intsetRemove 函數, 將隨機元素從整數集合中刪除掉。 調用 dictGetRandomKey 函數, 從字典中隨機取出一個字典鍵, 在將這個隨機字典鍵的值返回給客戶端以後, 調用 dictDelete 函數, 從字典中刪除隨機字典鍵所對應的鍵值對。
SREM 調用 intsetRemove 函數, 從整數集合中刪除全部給定的元素。 調用 dictDelete 函數, 從字典中刪除全部鍵爲給定元素的鍵值對。

 

(6)內存回收

對象的引用計數信息會隨着對象的使用狀態而不斷變化:

  • 在建立一個新對象時, 引用計數的值會被初始化爲 1 ;
  • 當對象被一個新程序使用時, 它的引用計數值會被增一;
  • 當對象再也不被一個程序使用時, 它的引用計數值會被減一;
  • 當對象的引用計數值變爲 0 時, 對象所佔用的內存會被釋放。

(7)對象共享

Redis 中, 讓多個鍵共享同一個值對象須要執行如下兩個步驟:

  1. 將數據庫鍵的值指針指向一個現有的值對象;
  2. 將被共享的值對象的引用計數增一。

目前來講, Redis 會在初始化服務器時, 建立一萬個字符串對象, 這些對象包含了從 0 到 9999 的全部整數值, 當服務器須要用到值爲 0到 9999 的字符串對象時, 服務器就會使用這些共享對象, 而不是新建立對象。

爲何 Redis 不共享包含字符串的對象?

當服務器考慮將一個共享對象設置爲鍵的值對象時, 程序須要先檢查給定的共享對象和鍵想建立的目標對象是否徹底相同, 只有在共享對象和目標對象徹底相同的狀況下, 程序纔會將共享對象用做鍵的值對象, 而一個共享對象保存的值越複雜, 驗證共享對象和目標對象是否相同所需的複雜度就會越高, 消耗的 CPU 時間也會越多:

  • 若是共享對象是保存整數值的字符串對象, 那麼驗證操做的複雜度爲 O(1) ;
  • 若是共享對象是保存字符串值的字符串對象, 那麼驗證操做的複雜度爲 O(N) ;
  • 若是共享對象是包含了多個值(或者對象的)對象, 好比列表對象或者哈希對象, 那麼驗證操做的複雜度將會是 O(N^2) 。

所以, 儘管共享更復雜的對象能夠節約更多的內存, 但受到 CPU 時間的限制, Redis 只對包含整數值的字符串對象進行共享。

(8)對象的空轉時長

 鍵的空轉時長還有另一項做用: 若是服務器打開了 maxmemory 選項, 而且服務器用於回收內存的算法爲 volatile-lru 或者 allkeys-lru , 那麼當服務器佔用的內存數超過了 maxmemory 選項所設置的上限值時, 空轉時長較高的那部分鍵會優先被服務器釋放, 從而回收內存。

重點回顧

  • Redis 數據庫中的每一個鍵值對的鍵和值都是一個對象。
  • Redis 共有字符串、列表、哈希、集合、有序集合五種類型的對象, 每種類型的對象至少都有兩種或以上的編碼方式, 不一樣的編碼能夠在不一樣的使用場景上優化對象的使用效率。
  • 服務器在執行某些命令以前, 會先檢查給定鍵的類型可否執行指定的命令, 而檢查一個鍵的類型就是檢查鍵的值對象的類型。
  • Redis 的對象系統帶有引用計數實現的內存回收機制, 當一個對象再也不被使用時, 該對象所佔用的內存就會被自動釋放。
  • Redis 會共享值爲 0 到 9999 的字符串對象。
  • 對象會記錄本身的最後一次被訪問的時間, 這個時間能夠用於計算對象的空轉時間。
相關文章
相關標籤/搜索