redisObject

簡介

redisObject:即redis對象临庇,redis數(shù)據(jù)庫是以Key-Value形式存在假夺,當(dāng)新建一個Key-Value對時,至少會創(chuàng)建兩個對象已卷,一個用于作為Key對象侧蘸,一個用于作為Value對象讳癌,每個對象都由一個redisObject的結(jié)構(gòu)表示。

數(shù)據(jù)結(jié)構(gòu)

redisObject數(shù)據(jù)結(jié)構(gòu)如下(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對象所保存的值對應(yīng)編碼埋虹,也就是數(shù)據(jù)結(jié)構(gòu)娩怎。

#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記錄了對象空轉(zhuǎn)時長截亦,OBJECT IDLETIME 命令可以打印出給定鍵的空轉(zhuǎn)時長。

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

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

如果服務(wù)器打開了 maxmemory 選項颖系, 并且服務(wù)器用于回收內(nèi)存的算法為 volatile-lru 或者 allkeys-lru 嘁扼, 那么當(dāng)服務(wù)器占用的內(nèi)存數(shù)超過了 maxmemory 選項所設(shè)置的上限值時, 空轉(zhuǎn)時長較高的那部分鍵會優(yōu)先被服務(wù)器釋放强缘, 從而回收內(nèi)存旅掂。

refcount

Redis中為了更好的優(yōu)化內(nèi)存空間访娶,對數(shù)字字符串進(jìn)行了共享內(nèi)存的操作,并以引用計數(shù)方式進(jìn)行管理(object.c)

// 將對象的引用計數(shù)值設(shè)置為 0 , 但并不釋放對象戳晌, 這個函數(shù)通常在需要重新設(shè)置對象的引用計數(shù)值時使用。
robj *resetRefCount(robj *obj) {
    obj->refcount = 0;
    return obj;
}
// 將對象的引用計數(shù)值增一
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");
        }
    }
}
// 將對象的引用計數(shù)值減一, 當(dāng)對象的引用計數(shù)值等于 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. 對一個對象進(jìn)行共享時囊嘉,這個對象的 refcount +1扭粱;
  3. 當(dāng)使用完一個對象之后震檩,或者取消對共享對象的引用之后,程序?qū)ο蟮?refcount -1博其;
  4. 當(dāng)對象的 refcount 降至 0 時贺奠,釋放redisObject 對象及*ptr引用的數(shù)據(jù)結(jié)構(gòu)错忱。

*ptr

*ptr 是一個指針以清,指向?qū)嶋H保存值的數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)由 type 屬性和 encoding 屬性決定眉孩。

假設(shè)redisObject 的 type = OBJ_ZSET勒葱, encoding = OBJ_ENCODING_SKIPLIST 凛虽,代表當(dāng)前redisObject是一個基于采用跳躍列表數(shù)據(jù)結(jié)構(gòu)的有序集合凯旋,*ptr指向?qū)?yīng)的跳躍列表钉迷。

類型檢查和命令多態(tài)

當(dāng)執(zhí)行一個處理數(shù)據(jù)類型的命令時糠聪, Redis 執(zhí)行以下步驟:

  1. 根據(jù)給定 key 舰蟆,在數(shù)據(jù)庫字典中查找和它相對應(yīng)的 redisObject 夭苗,如果沒找到,就返回 NULL 题造。
  2. 判斷 redisObject 的 type屬性 = 執(zhí)行命令所需的類型(HSET) 界赔?牵触,如果不相符揽思,返回類型錯誤。
  3. 根據(jù) redisObject 的 encoding 屬性所指定的編碼钉汗,選擇合適的操作函數(shù)來處理底層的數(shù)據(jù)結(jié)構(gòu)损痰。
  4. 返回數(shù)據(jù)結(jié)構(gòu)的操作結(jié)果作為命令的返回值卢未。

因此辽社,上述第2步就是類型檢查滴铅,第3步就是命令多態(tài)。

對象共享

除了用于實現(xiàn)引用計數(shù)內(nèi)存回收機制之外譬淳, 對象的引用計數(shù)屬性還帶有對象共享的作用邻梆。
假設(shè)鍵 key1創(chuàng)建了一個包含整數(shù)值 1 的字符串對象作為值對象浦妄,這時鍵key2也要創(chuàng)建一個同樣保存了整數(shù)值 1 的字符串對象作為值對象见芹,為了讓多個鍵共享同一個值對象需要執(zhí)行以下兩個步驟:

  1. 將數(shù)據(jù)庫鍵的值指針指向一個現(xiàn)有的值對象玄呛;
  2. 將被共享的值對象的引用計數(shù)增一徘铝。

目前來說惕它, Redis 會在初始化服務(wù)器時, 創(chuàng)建一萬個字符串對象郁惜, 這些對象包含了從 0 到 9999 的所有整數(shù)值兆蕉, 當(dāng)服務(wù)器需要用到值為 0到 9999 的字符串對象時恨樟, 服務(wù)器就會使用這些共享對象疚俱, 而不是新創(chuàng)建對象呆奕。
創(chuàng)建共享字符串對象的數(shù)量可以通過修改 server.h/OBJ_SHARED_INTEGERS常量來修改梁钾。

redis> SET key1 1
OK

redis> OBJECT REFCOUNT key1 
(integer) 2

該引用計數(shù)=2绳泉,是因為服務(wù)器程序初始化時,內(nèi)置了該對象了姆泻,refcount=1零酪。

另外冒嫡, 這些共享對象不單單只有字符串鍵可以使用, 那些在數(shù)據(jù)結(jié)構(gòu)中嵌套了字符串對象的對象(linkedlist 編碼的列表對象四苇、 hashtable 編碼的哈希對象孝凌、 hashtable 編碼的集合對象、以及 zset 編碼的有序集合對象)都可以使用這些共享對象月腋。

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

  • 如果共享對象是保存整數(shù)值的字符串對象色徘, 那么驗證操作的復(fù)雜度為 O(1);
  • 如果共享對象是保存字符串值的字符串對象斤寂, 那么驗證操作的復(fù)雜度為 O(N)器腋;
  • 如果共享對象是包含了多個值(或者對象的)對象诊县, 比如列表對象或者哈希對象怎披, 那么驗證操作的復(fù)雜度將會是 O(N^2) 缴渊。

    因此, 盡管共享更復(fù)雜的對象可以節(jié)約更多的內(nèi)存, 但受到 CPU 時間的限制, Redis 只對包含整數(shù)值的字符串對象進(jìn)行共享搓劫。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市深员,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖燎悍,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奏路,死亡現(xiàn)場離奇詭異斜脂,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門氛濒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炬灭,“玉大人米愿,你說我怎么就攤上這事。” “怎么了漱竖?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我遇西,道長,這世上最難降的妖魔是什么称近? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上送粱,老公的妹妹穿的比我還像新娘世舰。我一直安慰自己,他們只是感情好翔横,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布掘托。 她就那樣靜靜地躺著听绳,像睡著了一般鼠证。 火紅的嫁衣襯著肌膚如雪娩鹉。 梳的紋絲不亂的頭發(fā)上受楼,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天馋艺,我揣著相機與錄音,去河邊找鬼。 笑死圈浇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播梆造,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼箍镜,長吁一口氣:“原來是場噩夢啊……” “哼图张!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹲诀,沒想到半個月后尚揣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年秽之,在試婚紗的時候發(fā)現(xiàn)自己被綠了申鱼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匣砖。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖姻几,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情络拌,我是刑警寧澤盒音,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布维哈,位于F島的核電站,受9級特大地震影響迂求,放射性物質(zhì)發(fā)生泄漏励背。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一芹枷、第九天 我趴在偏房一處隱蔽的房頂上張望衅疙。 院中可真熱鬧,春花似錦鸳慈、人聲如沸饱溢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绩郎。三九已至,卻和暖如春翁逞,著一層夾襖步出監(jiān)牢的瞬間肋杖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工挖函, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留状植,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓怨喘,卻偏偏與公主長得像津畸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子必怜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容