redis redisObject 數據結構

在 Redis 的命令中, 用於對鍵 (key) 進行處理的命令佔了很大一部分, 而對於鍵所保存的值的類型, 鍵能執行的命令又各不相同.redis

好比說, LPUSHLLEN 只能用於列表鍵, 而 SADDSRANDMEMBER 只能用於集合鍵, 等等.數據庫

另一些命令, 好比 DELTTLTYPE, 能夠用於任何類型的鍵, 可是, 要正確實現這些命令, 必須爲不一樣類型的鍵設置不一樣的處理方式: 好比說, 刪除一個列表鍵和刪除一個字符串鍵的操做過程就不太同樣.數據結構

以上的描述說明, Redis 必須讓每一個鍵都帶有類型信息, 使得程序能夠檢查鍵的類型, 併爲它選擇合適的處理方式.函數

好比說, 集合類型就能夠由字典和整數集合兩種不一樣的數據結構實現, 可是, 當用戶執行 ZADD 命令時, 他/她應該沒必要關心集合使用的是什麼編碼, 只要 Redis 能按照 ZADD 命令的指示, 將新元素添加到集合就能夠了。編碼

這說明, 操做數據類型的命令除了要對鍵的類型進行檢查以外, 還須要根據數據類型的不一樣編碼進行多態處理.spa

爲了解決以上問題, Redis 構建了本身的類型系統, 這個系統的主要功能包括:指針

  • redisObject 對象.
  • 基於 redisObject 對象的類型檢查.
  • 基於 redisObject 對象的顯式多態函數.
  • 對 redisObject 進行分配、共享和銷燬的機制.

redisObject 是 Redis 類型系統的核心, 數據庫中的每一個鍵、值, 以及 Redis 自己處理的參數, 都表示爲這種數據類型.code

/*
 * Redis 對象
 */
typedef struct redisObject {

    // 類型
    unsigned type:4;

    // 對齊位
    unsigned notused:2;

    // 編碼方式
    unsigned encoding:4;

    // LRU 時間(相對於 server.lruclock)
    unsigned lru:22;

    // 引用計數
    int refcount;

    // 指向對象的值
    void *ptr;

} robj;

type、 encoding 和 ptr 是最重要的三個屬性.server

type 記錄了對象所保存的值的類型, 它的值多是如下常量的其中一個.對象

/*
 * 對象類型
 */
#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            // 編碼爲字符串
#define REDIS_ENCODING_INT 1            // 編碼爲整數
#define REDIS_ENCODING_HT 2             // 編碼爲哈希表
#define REDIS_ENCODING_ZIPMAP 3         // 編碼爲 zipmap
#define REDIS_ENCODING_LINKEDLIST 4     // 編碼爲雙端鏈表
#define REDIS_ENCODING_ZIPLIST 5        // 編碼爲壓縮列表
#define REDIS_ENCODING_INTSET 6         // 編碼爲整數集合
#define REDIS_ENCODING_SKIPLIST 7       // 編碼爲跳躍表

也就是經過 encoding 來對鍵進行不一樣的操做.

ptr 是一個指針, 指向實際保存值的數據結構, 這個數據結構由 type 屬性和 encoding 屬性決定.

舉個例子, 若是一個 redisObject 的 type 屬性爲 REDIS_LIST, encoding 屬性爲 REDIS_ENCODING_LINKEDLIST, 那麼這個對象就是一個 Redis 列表, 它的值保存在一個雙端鏈表內, 而 ptr 指針就指向這個雙端鏈表;

另外一方面, 若是一個 redisObject 的 type 屬性爲 REDIS_HASH, encoding 屬性爲 REDIS_ENCODING_ZIPMAP, 那麼這個對象就是一個 Redis 哈希表, 它的值保存在一個 zipmap 裏, 而 ptr 指針就指向這個 zipmap.

下圖展現了 redisObject 、Redis 全部數據類型、以及 Redis 全部編碼方式(底層實現)三者之間的關係:

clipboard.png

命令的類型檢查和多態

有了 redisObject 結構的存在, 在執行處理數據類型的命令時, 進行類型檢查和對編碼進行多態操做就簡單得多了.

當執行一個處理數據類型的命令時, Redis 執行如下步驟:

  1. 根據給定 key, 在數據庫字典中查找和它相對應的 redisObject, 若是沒找到, 就返回 NULL.
  2. 檢查 redisObject 的 type 屬性和執行命令所需的類型是否相符,若是不相符,返回類型錯誤.
  3. 根據 redisObject 的 encoding 屬性所指定的編碼,選擇合適的操做函數來處理底層的數據結構.
  4. 返回數據結構的操做結果做爲命令的返回值.

做爲例子,如下展現了對鍵 key 執行 LPOP 命令的完整過程:

clipboard.png

值得注意的是:
我下載了兩個版本的 Redis, 5.0.5 和 3.0.7.
其中 5.0.5 存在 server.h 中, 3.0.7 存在 redis.h 中.
可使 find ./ -name "*" | xargs grep "redisObject" 搜索如下.
相關文章
相關標籤/搜索