《redis設(shè)計(jì)與實(shí)現(xiàn)》——對(duì)象

本節(jié)介紹五種類(lèi)型的對(duì)象、對(duì)象對(duì)應(yīng)的編碼和相應(yīng)的底層實(shí)現(xiàn)

一挚赊、對(duì)象的類(lèi)型和編碼

  • Redis 使用對(duì)象來(lái)表示數(shù)據(jù)庫(kù)中的鍵和值。當(dāng)我們?cè)赗edis中新創(chuàng)建一個(gè)鍵值對(duì)時(shí)捂襟,我們至少創(chuàng)建兩個(gè)對(duì)象咬腕,一個(gè)對(duì)象用作鍵值對(duì)的鍵(鍵對(duì)象)掷漱,另一個(gè)對(duì)象用作鍵值對(duì)的值(值對(duì)象)腊凶。

  • 每一個(gè)對(duì)象都由一個(gè)redisObject結(jié)構(gòu)表示:

  • typedef struct redisObject {
        // 類(lèi)型
        unsigned type:4;
        
        // 編碼
        unsigned encoding:4;
        
        // 指向底層實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針
        void *ptr;
        
        // ...
        
    } robj;
    

五種對(duì)象類(lèi)型以及TYPE命令輸出

對(duì)象 對(duì)象type屬性的值 TYPE命令的輸出
字符串對(duì)象 REDIS_STRING "string"
列表對(duì)象 REDIS_LIST "list"
哈希對(duì)象 REDIS_HASH "hash"
集合對(duì)象 REDIS_SET set
有序集合對(duì)象 REDIS_ZSET "zset"

對(duì)象的編碼

encoding屬性記錄了對(duì)象使用的編碼,也即是說(shuō)這個(gè)對(duì)象使用了什么數(shù)據(jù)結(jié)構(gòu)作為對(duì)象的底層實(shí)現(xiàn)抡四。

編碼常量 編碼對(duì)應(yīng)的底層數(shù)據(jù)結(jié)構(gòu)
REDIS_ENCODING_INT long類(lèi)型的整數(shù)
REDIS_ENCODING_EMBSTR enbstr編碼的簡(jiǎn)單動(dòng)態(tài)字符串
REDIS_ENCODING_RAW 簡(jiǎn)單動(dòng)態(tài)字符串
REDIS_ENCODING_HT 字典
REDIS_ENCODING_LINKEDLIST 雙端鏈表
REDIS_ENCODING_ZIPLIST 壓縮列表
REDIS_ENCODING_INTSET 整數(shù)集合
REDIS_ENCODING_SKIPLIST 跳躍表和字典

不同類(lèi)型和編碼的對(duì)象

同一對(duì)象類(lèi)型在不同條件下使用不同編碼宠漩,對(duì)應(yīng)不同實(shí)現(xiàn)举反。

類(lèi)型 編碼 對(duì)象
REDIS_STRING REDIS_ENCODING_INT 使用整數(shù)值實(shí)現(xiàn)的字符串對(duì)象
REDIS_STRING REDIS_ENCODING_ENBSTR 使用embstr編碼的簡(jiǎn)單動(dòng)態(tài)字符串實(shí)現(xiàn)的字符串對(duì)象
REDIS_STRING REDIS_ENCODING_RAW 使用簡(jiǎn)單動(dòng)態(tài)字符串實(shí)現(xiàn)的字符串的對(duì)象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用壓縮列表實(shí)現(xiàn)的列表對(duì)象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用雙端鏈表實(shí)現(xiàn)的列表對(duì)象
REDIS_HSAH REDIS_ENCODING_ZIPLIST 使用壓縮列表實(shí)現(xiàn)的哈希對(duì)象
REDIS_HASH REDIS_ENCODING_HT 使用字典實(shí)現(xiàn)的哈希對(duì)象
REDIS_SET REDIS_ENCODING_INTSET 使用整數(shù)集合實(shí)現(xiàn)的集合對(duì)象
REDIS_SET REDIS_ENCODING_HT 使用字典實(shí)現(xiàn)的集合對(duì)象
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用壓縮列表實(shí)現(xiàn)的有序集合對(duì)象
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用條約表和字典實(shí)現(xiàn)的有序集合對(duì)象

使用OBJECT ENCODING命令可以查看一個(gè)數(shù)據(jù)庫(kù)的鍵的值對(duì)象的編碼。

> OBJECT ENCODING key_name

二扒吁、字符串對(duì)象

字符串對(duì)象的編碼可以是int火鼻、raw或者enbstr。

如果一個(gè)字符串對(duì)象保存到的是整數(shù)值雕崩,并且這個(gè)整數(shù)值可以用long類(lèi)型來(lái)表示魁索,那么字符串對(duì)象會(huì)將整數(shù)值保存在字符串對(duì)象結(jié)構(gòu)的ptr屬性里面(將void* 轉(zhuǎn)換成long)并將字符串對(duì)象的編碼設(shè)置為int。

如果字符串對(duì)象保存的是一個(gè)字符串值盼铁,并且字符串長(zhǎng)度大于39字節(jié)粗蔚,那么字符串對(duì)象使用一個(gè)簡(jiǎn)單動(dòng)態(tài)字符串(SDS)來(lái)保存這個(gè)字符串值,并將對(duì)象的編碼設(shè)置為raw饶火。

如果字符串對(duì)象保存的是一個(gè)字符串值鹏控,并且這個(gè)字符串的長(zhǎng)度小于39字節(jié),那么字符串對(duì)象將使用embstr編碼的方式來(lái)保存這個(gè)字符串的值肤寝。

使用embstr編碼的字符串對(duì)象保存短字符串好處:

  • 創(chuàng)建字符串對(duì)象所需內(nèi)存分配數(shù)從raw編碼的兩次降低為一次当辐。釋放對(duì)象也只需調(diào)用一次內(nèi)存釋放函數(shù)。
  • 所有數(shù)據(jù)都保存在一塊連續(xù)的內(nèi)存里面鲤看,能夠更好地利用緩存帶來(lái)的優(yōu)勢(shì)缘揪。

編碼轉(zhuǎn)換

  • 對(duì)int編碼的字符串執(zhí)行一些命令使這個(gè)對(duì)象報(bào)保存的不再是整數(shù)值,而是一個(gè)字符串值,那么字符串對(duì)象的編碼將從int變成raw寺晌。
  • embstr編碼的字符串對(duì)象是只讀的世吨。當(dāng)我們對(duì)embstr編碼的字符串對(duì)象執(zhí)行任何修改命令時(shí),程序會(huì)先將對(duì)象的編碼從embstr轉(zhuǎn)換成raw呻征,然后在執(zhí)行修改命令耘婚。這也是為什么int編碼的字符串對(duì)象進(jìn)行編碼轉(zhuǎn)換只能轉(zhuǎn)換成raw編碼的字符串對(duì)象。

三陆赋、列表對(duì)象

列表對(duì)象的編碼可以是ziplist或者linkedlist沐祷。

linkedlist編碼的列表對(duì)象使用雙端鏈表作為底層實(shí)現(xiàn),每個(gè)雙端鏈表節(jié)點(diǎn)(node)都保存了一個(gè)字符串對(duì)象攒岛,而每個(gè)字符串對(duì)象都保存了一個(gè)列表元素赖临。

這種嵌套字符串對(duì)象的行為在后面介紹的哈希對(duì)象、集合對(duì)象和有序集合對(duì)象中都會(huì)出現(xiàn)灾锯,字符串對(duì)象是redis五種類(lèi)型的對(duì)象唯一一種會(huì)被其他四種對(duì)象嵌套的對(duì)象兢榨。

編碼轉(zhuǎn)換

同時(shí)滿足以下兩個(gè)條件,列表對(duì)象使用ziplist編碼:

  • 列表對(duì)象保存的所有字符串元素長(zhǎng)度都小于64字節(jié)顺饮;
  • 列表對(duì)象保存的元素?cái)?shù)量小于512個(gè)吵聪;
  • 不能滿足這兩個(gè)條件的列表對(duì)象需要使用linkedlist編碼。

四兼雄、哈希對(duì)象

哈希對(duì)象的編碼可以是ziplist和hashtable吟逝。

ziplist編碼的哈希對(duì)象

  • 先將保存了鍵的壓縮列表節(jié)點(diǎn)推入到壓縮列表表尾,然后再將保存了值的壓縮列表節(jié)點(diǎn)推入到壓縮列表表尾赦肋。兩個(gè)節(jié)點(diǎn)緊挨在一起块攒,鍵節(jié)點(diǎn)在前,值節(jié)點(diǎn)在后佃乘。
  • 尾插法囱井。

hashtable編碼的哈希對(duì)象使用字典作為底層實(shí)現(xiàn)

  • 字典的每個(gè)鍵都是一個(gè)字符串對(duì)象,對(duì)象中保存了鍵值對(duì)的鍵趣避;
  • 字典的每個(gè)值都是一個(gè)字符串對(duì)象琅绅,對(duì)象中保存了鍵值對(duì)的值;

編碼轉(zhuǎn)換:

滿足以下兩個(gè)條件鹅巍,哈希對(duì)象使用ziplist編碼:

  • 哈希對(duì)象保存的所有鍵值對(duì)的鍵和值的字符串長(zhǎng)度都小于64字節(jié);
  • 哈希對(duì)象保存的鍵值對(duì)數(shù)量小于512個(gè)料祠;
  • 不能滿足這兩個(gè)條件的哈希對(duì)象需要使用hashtable編碼骆捧。

五、集合對(duì)象

集合對(duì)象的編碼可以是intset或者h(yuǎn)ashtable髓绽。

編碼轉(zhuǎn)換

滿足以下兩個(gè)條件敛苇,對(duì)象使用intset編碼:

  • 集合對(duì)象保存的所有元素都是整數(shù)值;
  • 集合對(duì)象保存的元素?cái)?shù)量不超過(guò)512個(gè);
  • 不能滿足這兩個(gè)條件的集合對(duì)象需要使用hashtable編碼枫攀。

六括饶、有序集合對(duì)象

有序集合的編碼可以是ziplist或者skiplist。

skiplist編碼的有序集合對(duì)象使用zset結(jié)構(gòu)作為底層實(shí)現(xiàn)来涨,一個(gè)zset結(jié)構(gòu)同時(shí)包含一個(gè)字典和一個(gè)跳躍表:

typedef struct zset {
    zskiplist *zsl;
    dict *dict;
} zset;

為什么有序集合需要同時(shí)使用跳躍表和字典來(lái)實(shí)現(xiàn)图焰?

  • 使用跳躍表:跳躍表節(jié)點(diǎn)的object屬性保存了元素的成員,而跳躍表節(jié)點(diǎn)的score屬性保存了元素的分值蹦掐。通過(guò)這個(gè)跳躍表技羔,程序可以對(duì)有序集合進(jìn)行范圍型操作,比如ZRANK卧抗、ZRANGE等命令就是基于跳躍表API來(lái)實(shí)現(xiàn)的藤滥。
  • 使用字典:程序可以用O(1)復(fù)雜度查找給定成員的分值,ZSCORE命令就是根據(jù)這一特性實(shí)現(xiàn)的社裆。
  • 須知:在實(shí)際中拙绊,字典和跳躍表會(huì)共享元素的成員和分值,所以并不會(huì)造成任何數(shù)據(jù)重復(fù)泳秀,也不會(huì)因此浪費(fèi)任何內(nèi)存标沪。

編碼轉(zhuǎn)換

同時(shí)滿足以下兩個(gè)條件,對(duì)象使用ziplist編碼:

  • 有序集合保存的元素?cái)?shù)量小于128個(gè)晶默;
  • 有序集合保存的所有元素成員的長(zhǎng)度都小于64字節(jié)谨娜。
  • 不滿足以兩個(gè)條件的有序集合對(duì)象都將使用skiplist編碼。

七磺陡、類(lèi)型檢查與命令多態(tài)

Redis用于操作鍵的命令基本可以分為兩種類(lèi)型:

  • 命令可以對(duì)任何類(lèi)型的鍵執(zhí)行趴梢。比如DEL命令、EXPIRE命令币他、RENAME命令坞靶、TYPE命令、OBJECT命令等蝴悉。

  • 另一種命令只能對(duì)特定類(lèi)型的鍵執(zhí)行彰阴,比如說(shuō):

    SET, GET, APPEND, STRLEN等命令只能對(duì)字符串鍵執(zhí)行;
    ...
    

類(lèi)型檢查

在執(zhí)行一個(gè)類(lèi)型特定的命令之前拍冠,Redis會(huì)先檢查輸入鍵的類(lèi)型是否正確尿这,然后再?zèng)Q定是否執(zhí)行給定的命令。

類(lèi)型檢查:redisObject.type == target_type ?

多態(tài)命令的實(shí)現(xiàn)

Redis會(huì)根據(jù)值對(duì)象的編碼方式庆杜,選擇正確的命令實(shí)現(xiàn)代碼來(lái)執(zhí)行命令射众。

比如:只要執(zhí)行LLEN命名的是列表鍵,那么無(wú)論值對(duì)象使用的是ziplist還是linkedlist編碼晃财,命令都可以正常實(shí)行叨橱。

DEL、EXPIRE等命令和LLEN等命令的區(qū)別在于,前者是基于類(lèi)型的多態(tài)——一個(gè)命令可以同時(shí)處理不同類(lèi)型的鍵罗洗,而后者是基于編碼的多態(tài)——一個(gè)命令可以同時(shí)處理多種不同編碼愉舔。

八、內(nèi)存回收

因?yàn)镃語(yǔ)言并不具備自動(dòng)內(nèi)存回收功能伙菜,所以Redis在自己的對(duì)象系統(tǒng)中構(gòu)建了一個(gè)引用計(jì)數(shù)(reference counting)技術(shù)實(shí)現(xiàn)的內(nèi)存回收機(jī)制轩缤。

typedef struct redisObject {
    // ...
    // 引用計(jì)數(shù)
    // ...
} robj;

九、對(duì)象共享

除了用于實(shí)現(xiàn)引用計(jì)數(shù)內(nèi)存回收機(jī)制之外仇让,對(duì)象的引用計(jì)數(shù)屬性還帶有對(duì)象共享的作用典奉。

實(shí)現(xiàn)機(jī)制

  • 將數(shù)據(jù)庫(kù)鍵的值指針指向一個(gè)現(xiàn)有的值對(duì)象;
  • 將被共享的值對(duì)象的引用計(jì)數(shù)增一丧叽。

共享對(duì)象機(jī)制對(duì)于節(jié)約內(nèi)訓(xùn)非常有幫助卫玖,數(shù)據(jù)庫(kù)中保存的相同值越多,對(duì)象共享機(jī)制就越能節(jié)約越多的內(nèi)存踊淳。

為什么Redis不共享包含字符串的對(duì)象假瞬?

  • 一個(gè)共享對(duì)象保存的值越復(fù)雜,驗(yàn)證共享對(duì)象和目標(biāo)對(duì)象是否相同所需的復(fù)雜度就會(huì)越高迂尝,消耗CPU時(shí)間也會(huì)越多脱茉。
  • 如果共享對(duì)象是保存整數(shù)值的字符串對(duì)象,那么驗(yàn)證操作的復(fù)雜度為O(1);
  • 如果共享對(duì)象是保存字符串值的字符串對(duì)象垄开,那么驗(yàn)證操作的復(fù)雜度為O(N)琴许;
  • 受到CPU時(shí)間的限制,Redis只對(duì)包含整數(shù)值的字符串對(duì)象進(jìn)行共享溉躲。
  • 目前來(lái)書(shū)榜田,Redis在初始化服務(wù)器時(shí),創(chuàng)建一萬(wàn)個(gè)字符串對(duì)象锻梳,這些對(duì)象包含了從0到9999的所有整數(shù)值箭券,當(dāng)服務(wù)器需要用到值為0到9999的字符串對(duì)象時(shí),服務(wù)器就會(huì)使用這些共享對(duì)象疑枯,而不是新創(chuàng)建對(duì)象辩块。

十、對(duì)象的空轉(zhuǎn)時(shí)長(zhǎng)

除了前面介紹過(guò)的type荆永、encoding废亭、ptr和refcount四個(gè)屬性之外,redisObject結(jié)構(gòu)包含的最后一個(gè)屬性為lru屬性具钥,該屬性記錄了對(duì)象最后一i此被命令程序訪問(wèn)的時(shí)間:

typedef struct redisObject {
    // ...
    unsigned lru:22;
    // ...
}robj;

空轉(zhuǎn)時(shí)長(zhǎng)就是當(dāng)前時(shí)間減去鍵的值對(duì)象的lru時(shí)間計(jì)算得出的豆村。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市氓拼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖桃漾,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坏匪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡撬统,警方通過(guò)查閱死者的電腦和手機(jī)适滓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)恋追,“玉大人凭迹,你說(shuō)我怎么就攤上這事】啻眩” “怎么了嗅绸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)撕彤。 經(jīng)常有香客問(wèn)我鱼鸠,道長(zhǎng),這世上最難降的妖魔是什么羹铅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任蚀狰,我火速辦了婚禮,結(jié)果婚禮上职员,老公的妹妹穿的比我還像新娘麻蹋。我一直安慰自己,他們只是感情好焊切,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布扮授。 她就那樣靜靜地躺著,像睡著了一般蛛蒙。 火紅的嫁衣襯著肌膚如雪糙箍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天牵祟,我揣著相機(jī)與錄音深夯,去河邊找鬼。 笑死诺苹,一個(gè)胖子當(dāng)著我的面吹牛咕晋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播收奔,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼掌呜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了坪哄?” 一聲冷哼從身側(cè)響起质蕉,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤势篡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后模暗,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體禁悠,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年兑宇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碍侦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隶糕,死狀恐怖瓷产,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情枚驻,我是刑警寧澤濒旦,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站测秸,受9級(jí)特大地震影響疤估,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霎冯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一铃拇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沈撞,春花似錦慷荔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至壹士,卻和暖如春磷雇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躏救。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工唯笙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盒使。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓崩掘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親少办。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苞慢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354