redisObject

簡介

redisObject:即redis對象,redis數據庫是以Key-Value形式存在,當新建一個Key-Value對時,至少會創建兩個對象,一個用于作為Key對象,一個用于作為Value對象,每個對象都由一個redisObject的結構表示。

數據結構

redisObject數據結構如下(server.h):

#define LRU_BITS 24
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

type

表示redis對象所保存的值的類型。

/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

encoding

表示redis對象所保存的值對應編碼,也就是數據結構。

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

lru

lru記錄了對象空轉時長,OBJECT IDLETIME 命令可以打印出給定鍵的空轉時長。

redis> SET key1 "hello world"
OK
# 過段時間執行
redis> OBJECT IDLETIME key1
(integer) 20

OBJECT IDLETIME命令在訪問鍵的值對象時, 不會更新值對象的 lru 屬性。

如果服務器打開了 maxmemory 選項, 并且服務器用于回收內存的算法為 volatile-lru 或者 allkeys-lru , 那么當服務器占用的內存數超過了 maxmemory 選項所設置的上限值時, 空轉時長較高的那部分鍵會優先被服務器釋放, 從而回收內存。

refcount

Redis中為了更好的優化內存空間,對數字字符串進行了共享內存的操作,并以引用計數方式進行管理(object.c)

// 將對象的引用計數值設置為 0 , 但并不釋放對象, 這個函數通常在需要重新設置對象的引用計數值時使用。
robj *resetRefCount(robj *obj) {
    obj->refcount = 0;
    return obj;
}
// 將對象的引用計數值增一
void incrRefCount(robj *o) {
    if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {
        o->refcount++;
    } else {
        if (o->refcount == OBJ_SHARED_REFCOUNT) {
            /* Nothing to do: this refcount is immutable. */
        } else if (o->refcount == OBJ_STATIC_REFCOUNT) {
            serverPanic("You tried to retain an object allocated in the stack");
        }
    }
}
// 將對象的引用計數值減一, 當對象的引用計數值等于 0 時, 釋放對象。
void decrRefCount(robj *o) {
    if (o->refcount == 1) {
        switch(o->type) {
        case OBJ_STRING: freeStringObject(o); break;
        case OBJ_LIST: freeListObject(o); break;
        case OBJ_SET: freeSetObject(o); break;
        case OBJ_ZSET: freeZsetObject(o); break;
        case OBJ_HASH: freeHashObject(o); break;
        case OBJ_MODULE: freeModuleObject(o); break;
        case OBJ_STREAM: freeStreamObject(o); break;
        default: serverPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
        if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
    }
}
  1. 新建一個對象時,它的 refcount = 1 ;
  2. 對一個對象進行共享時,這個對象的 refcount +1;
  3. 當使用完一個對象之后,或者取消對共享對象的引用之后,程序將對象的 refcount -1;
  4. 當對象的 refcount 降至 0 時,釋放redisObject 對象及*ptr引用的數據結構。

*ptr

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

假設redisObject 的 type = OBJ_ZSET, encoding = OBJ_ENCODING_SKIPLIST ,代表當前redisObject是一個基于采用跳躍列表數據結構的有序集合,*ptr指向對應的跳躍列表。

類型檢查和命令多態

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

  1. 根據給定 key ,在數據庫字典中查找和它相對應的 redisObject ,如果沒找到,就返回 NULL 。
  2. 判斷 redisObject 的 type屬性 = 執行命令所需的類型(HSET) ?,如果不相符,返回類型錯誤。
  3. 根據 redisObject 的 encoding 屬性所指定的編碼,選擇合適的操作函數來處理底層的數據結構。
  4. 返回數據結構的操作結果作為命令的返回值。

因此,上述第2步就是類型檢查,第3步就是命令多態。

對象共享

除了用于實現引用計數內存回收機制之外, 對象的引用計數屬性還帶有對象共享的作用。
假設鍵 key1創建了一個包含整數值 1 的字符串對象作為值對象,這時鍵key2也要創建一個同樣保存了整數值 1 的字符串對象作為值對象,為了讓多個鍵共享同一個值對象需要執行以下兩個步驟:

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

目前來說, Redis 會在初始化服務器時, 創建一萬個字符串對象, 這些對象包含了從 0 到 9999 的所有整數值, 當服務器需要用到值為 0到 9999 的字符串對象時, 服務器就會使用這些共享對象, 而不是新創建對象。
創建共享字符串對象的數量可以通過修改 server.h/OBJ_SHARED_INTEGERS常量來修改。

redis> SET key1 1
OK

redis> OBJECT REFCOUNT key1 
(integer) 2

該引用計數=2,是因為服務器程序初始化時,內置了該對象了,refcount=1。

另外, 這些共享對象不單單只有字符串鍵可以使用, 那些在數據結構中嵌套了字符串對象的對象(linkedlist 編碼的列表對象、 hashtable 編碼的哈希對象、 hashtable 編碼的集合對象、以及 zset 編碼的有序集合對象)都可以使用這些共享對象。

為什么 Redis 不共享包含字符串的對象?
當服務器考慮將一個共享對象設置為鍵的值對象時, 程序需要先檢查給定的共享對象和鍵想創建的目標對象是否完全相同, 只有在共享對象目標對象完全相同的情況下, 程序才會將共享對象用作鍵的值對象, 而一個共享對象保存的值越復雜, 驗證共享對象目標對象是否相同所需的復雜度就會越高, 消耗的 CPU 時間也會越多:

  • 如果共享對象是保存整數值的字符串對象, 那么驗證操作的復雜度為 O(1);
  • 如果共享對象是保存字符串值的字符串對象, 那么驗證操作的復雜度為 O(N);
  • 如果共享對象是包含了多個值(或者對象的)對象, 比如列表對象或者哈希對象, 那么驗證操作的復雜度將會是 O(N^2) 。

    因此, 盡管共享更復雜的對象可以節約更多的內存, 但受到 CPU 時間的限制, Redis 只對包含整數值的字符串對象進行共享。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容