壓縮列表

構(gòu)成

壓縮列表是 Redis 為了節(jié)約內(nèi)存而開發(fā)的测蘑, 由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型(sequential)數(shù)據(jù)結(jié)構(gòu)墨礁。

一個(gè)壓縮列表可以包含任意多個(gè)節(jié)點(diǎn)(entry), 每個(gè)節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值。


image.png

圖 7-2 展示了一個(gè)壓縮列表示例:

  • 列表 zlbytes 屬性的值為 0x50 (十進(jìn)制 80), 表示壓縮列表的總長為 80 字節(jié)实昨。
  • 列表 zltail 屬性的值為 0x3c (十進(jìn)制 60), 這表示如果我們有一個(gè)指向壓縮列表起始地址的指針 p 盐固, 那么只要用指針 p 加上偏移量 60 荒给, 就可以計(jì)算出表尾節(jié)點(diǎn) entry3 的地址。
  • 列表 zllen 屬性的值為 0x3 (十進(jìn)制 3)刁卜, 表示壓縮列表包含三個(gè)節(jié)點(diǎn)志电。
image.png

圖 7-3 展示了另一個(gè)壓縮列表示例:

  • 列表 zlbytes 屬性的值為 0xd2 (十進(jìn)制 210), 表示壓縮列表的總長為 210 字節(jié)蛔趴。
  • 列表 zltail 屬性的值為 0xb3 (十進(jìn)制 179)溪北, 這表示如果我們有一個(gè)指向壓縮列表起始地址的指針 p , 那么只要用指針 p 加上偏移量 179 夺脾, 就可以計(jì)算出表尾節(jié)點(diǎn) entry5 的地址之拨。
  • 列表 zllen 屬性的值為 0x5 (十進(jìn)制 5), 表示壓縮列表包含五個(gè)節(jié)點(diǎn)咧叭。
image.png

每個(gè)壓縮列表節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值蚀乔, 其中, 字節(jié)數(shù)組可以是以下三種長度的其中一種:

  • 長度小于等于 63 (2^{6}-1)字節(jié)的字節(jié)數(shù)組菲茬;
  • 長度小于等于 16383 (2^{14}-1) 字節(jié)的字節(jié)數(shù)組吉挣;
  • 長度小于等于 4294967295 (2^{32}-1)字節(jié)的字節(jié)數(shù)組;

而整數(shù)值則可以是以下六種長度的其中一種:

  • 4 位長婉弹,介于 0 至 12 之間的無符號(hào)整數(shù)睬魂;
  • 1 字節(jié)長的有符號(hào)整數(shù);
  • 3 字節(jié)長的有符號(hào)整數(shù)镀赌;
  • int16_t 類型整數(shù)氯哮;
  • int32_t 類型整數(shù);
  • int64_t 類型整數(shù)商佛。
    每個(gè)壓縮列表節(jié)點(diǎn)都由 previous_entry_length 喉钢、 encoding 、 content 三個(gè)部分組成良姆, 如圖 7-4 所示肠虽。
image.png

壓縮列表節(jié)點(diǎn)介紹

節(jié)點(diǎn)的 previous_entry_length 屬性以字節(jié)為單位, 記錄了壓縮列表中前一個(gè)節(jié)點(diǎn)的長度玛追。

previous_entry_length 屬性的長度可以是 1 字節(jié)或者 5 字節(jié):

  • 如果前一節(jié)點(diǎn)的長度小于 254 字節(jié)税课, 那么 previous_entry_length 屬性的長度為 1 字節(jié): 前一節(jié)點(diǎn)的長度就保存在這一個(gè)字節(jié)里面闲延。
  • 如果前一節(jié)點(diǎn)的長度大于等于 254 字節(jié), 那么 previous_entry_length 屬性的長度為 5 字節(jié): 其中屬性的第一字節(jié)會(huì)被設(shè)置為 0xFE (十進(jìn)制值 254)韩玩, 而之后的四個(gè)字節(jié)則用于保存前一節(jié)點(diǎn)的長度慨代。

圖 7-5 展示了一個(gè)包含一字節(jié)長 previous_entry_length 屬性的壓縮列表節(jié)點(diǎn), 屬性的值為 0x05 啸如, 表示前一節(jié)點(diǎn)的長度為 5 字節(jié)。

image.png

圖 7-6 展示了一個(gè)包含五字節(jié)長 previous_entry_length 屬性的壓縮節(jié)點(diǎn)氮惯, 屬性的值為 0xFE00002766 叮雳, 其中值的最高位字節(jié) 0xFE 表示這是一個(gè)五字節(jié)長的 previous_entry_length 屬性, 而之后的四字節(jié) 0x00002766 (十進(jìn)制值 10086 )才是前一節(jié)點(diǎn)的實(shí)際長度妇汗。


image.png

因?yàn)楣?jié)點(diǎn)的 previous_entry_length 屬性記錄了前一個(gè)節(jié)點(diǎn)的長度帘不, 所以程序可以通過指針運(yùn)算, 根據(jù)當(dāng)前節(jié)點(diǎn)的起始地址來計(jì)算出前一個(gè)節(jié)點(diǎn)的起始地址杨箭。

舉個(gè)例子寞焙, 如果我們有一個(gè)指向當(dāng)前節(jié)點(diǎn)起始地址的指針 c , 那么我們只要用指針 c 減去當(dāng)前節(jié)點(diǎn) previous_entry_length 屬性的值互婿, 就可以得出一個(gè)指向前一個(gè)節(jié)點(diǎn)起始地址的指針 p 捣郊, 如圖 7-7 所示。


image.png

壓縮列表的從表尾向表頭遍歷操作就是使用這一原理實(shí)現(xiàn)的: 只要我們擁有了一個(gè)指向某個(gè)節(jié)點(diǎn)起始地址的指針慈参, 那么通過這個(gè)指針以及這個(gè)節(jié)點(diǎn)的 previous_entry_length 屬性呛牲, 程序就可以一直向前一個(gè)節(jié)點(diǎn)回溯, 最終到達(dá)壓縮列表的表頭節(jié)點(diǎn)驮配。

圖 7-8 展示了一個(gè)從表尾節(jié)點(diǎn)向表頭節(jié)點(diǎn)進(jìn)行遍歷的完整過程:

  • 首先娘扩,我們擁有指向壓縮列表表尾節(jié)點(diǎn) entry4 起始地址的指針 p1 (指向表尾節(jié)點(diǎn)的指針可以通過指向壓縮列表起始地址的指針加上 zltail 屬性的值得出);
  • 通過用 p1 減去 entry4 節(jié)點(diǎn) previous_entry_length 屬性的值壮锻, 我們得到一個(gè)指向 entry4 前一節(jié)點(diǎn) entry3 起始地址的指針 p2 琐旁;
  • 通過用 p2 減去 entry3 節(jié)點(diǎn) previous_entry_length 屬性的值, 我們得到一個(gè)指向 entry3 前一節(jié)點(diǎn) entry2 起始地址的指針 p3 猜绣;
  • 通過用 p3 減去 entry2 節(jié)點(diǎn) previous_entry_length 屬性的值灰殴, 我們得到一個(gè)指向 entry2 前一節(jié)點(diǎn) entry1 起始地址的指針 p4 , entry1 為壓縮列表的表頭節(jié)點(diǎn)掰邢;
  • 最終验懊, 我們從表尾節(jié)點(diǎn)向表頭節(jié)點(diǎn)遍歷了整個(gè)列表。
image.png

encoding

節(jié)點(diǎn)的 encoding 屬性記錄了節(jié)點(diǎn)的 content 屬性所保存數(shù)據(jù)的類型以及長度:

  • 一字節(jié)尸变、兩字節(jié)或者五字節(jié)長义图, 值的最高位為 00 、 01 或者 10 的是字節(jié)數(shù)組編碼: 這種編碼表示節(jié)點(diǎn)的 content 屬性保存著字節(jié)數(shù)組召烂, 數(shù)組的長度由編碼除去最高兩位之后的其他位記錄碱工;
  • 一字節(jié)長, 值的最高位以 11 開頭的是整數(shù)編碼: 這種編碼表示節(jié)點(diǎn)的 content 屬性保存著整數(shù)值, 整數(shù)值的類型和長度由編碼除去最高兩位之后的其他位記錄怕篷;

表 7-2 記錄了所有可用的字節(jié)數(shù)組編碼历筝, 而表 7-3 則記錄了所有可用的整數(shù)編碼。 表格中的下劃線 _ 表示留空廊谓, 而 b 梳猪、 x 等變量則代表實(shí)際的二進(jìn)制數(shù)據(jù), 為了方便閱讀蒸痹, 多個(gè)字節(jié)之間用空格隔開春弥。


image.png

content

節(jié)點(diǎn)的 content 屬性負(fù)責(zé)保存節(jié)點(diǎn)的值, 節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù)叠荠, 值的類型和長度由節(jié)點(diǎn)的 encoding 屬性決定匿沛。

圖 7-9 展示了一個(gè)保存字節(jié)數(shù)組的節(jié)點(diǎn)示例:

  • 編碼的最高兩位 00 表示節(jié)點(diǎn)保存的是一個(gè)字節(jié)數(shù)組;
  • 編碼的后六位 001011 記錄了字節(jié)數(shù)組的長度 11 榛鼎;
  • content 屬性保存著節(jié)點(diǎn)的值 "hello world" 逃呼。


圖 7-10 展示了一個(gè)保存整數(shù)值的節(jié)點(diǎn)示例:

  • 編碼 11000000 表示節(jié)點(diǎn)保存的是一個(gè) int16_t 類型的整數(shù)值;
  • content 屬性保存著節(jié)點(diǎn)的值 10086 者娱。


    image.png

連鎖更新

前面說過抡笼, 每個(gè)節(jié)點(diǎn)的 previous_entry_length 屬性都記錄了前一個(gè)節(jié)點(diǎn)的長度:

  • 如果前一節(jié)點(diǎn)的長度小于 254 字節(jié), 那么 previous_entry_length 屬性需要用 1 字節(jié)長的空間來保存這個(gè)長度值黄鳍。
  • 如果前一節(jié)點(diǎn)的長度大于等于 254 字節(jié)蔫缸, 那么 previous_entry_length 屬性需要用 5 字節(jié)長的空間來保存這個(gè)長度值。

現(xiàn)在际起, 考慮這樣一種情況: 在一個(gè)壓縮列表中拾碌, 有多個(gè)連續(xù)的、長度介于 250 字節(jié)到 253 字節(jié)之間的節(jié)點(diǎn) e1 至 eN 街望, 如圖 7-11 所示校翔。

image.png

因?yàn)?e1 至 eN 的所有節(jié)點(diǎn)的長度都小于 254 字節(jié), 所以記錄這些節(jié)點(diǎn)的長度只需要 1 字節(jié)長的 previous_entry_length 屬性灾前, 換句話說防症, e1 至 eN 的所有節(jié)點(diǎn)的 previous_entry_length 屬性都是 1 字節(jié)長的。

這時(shí)哎甲, 如果我們將一個(gè)長度大于等于 254 字節(jié)的新節(jié)點(diǎn) new 設(shè)置為壓縮列表的表頭節(jié)點(diǎn)蔫敲, 那么 new 將成為 e1 的前置節(jié)點(diǎn), 如圖 7-12 所示炭玫。


image.png

因?yàn)?e1 的 previous_entry_length 屬性僅長 1 字節(jié)奈嘿, 它沒辦法保存新節(jié)點(diǎn) new 的長度, 所以程序?qū)?duì)壓縮列表執(zhí)行空間重分配操作吞加, 并將 e1 節(jié)點(diǎn)的 previous_entry_length 屬性從原來的 1 字節(jié)長擴(kuò)展為 5 字節(jié)長裙犹。

現(xiàn)在尽狠, 麻煩的事情來了 —— e1 原本的長度介于 250 字節(jié)至 253 字節(jié)之間, 在為 previous_entry_length 屬性新增四個(gè)字節(jié)的空間之后叶圃, e1 的長度就變成了介于 254 字節(jié)至 257 字節(jié)之間袄膏, 而這種長度使用 1 字節(jié)長的 previous_entry_length 屬性是沒辦法保存的。

因此掺冠, 為了讓 e2 的 previous_entry_length 屬性可以記錄下 e1 的長度沉馆, 程序需要再次對(duì)壓縮列表執(zhí)行空間重分配操作, 并將 e2 節(jié)點(diǎn)的 previous_entry_length 屬性從原來的 1 字節(jié)長擴(kuò)展為 5 字節(jié)長德崭。

正如擴(kuò)展 e1 引發(fā)了對(duì) e2 的擴(kuò)展一樣斥黑, 擴(kuò)展 e2 也會(huì)引發(fā)對(duì) e3 的擴(kuò)展, 而擴(kuò)展 e3 又會(huì)引發(fā)對(duì) e4 的擴(kuò)展……為了讓每個(gè)節(jié)點(diǎn)的 previous_entry_length 屬性都符合壓縮列表對(duì)節(jié)點(diǎn)的要求接癌, 程序需要不斷地對(duì)壓縮列表執(zhí)行空間重分配操作, 直到 eN 為止扣讼。

Redis 將這種在特殊情況下產(chǎn)生的連續(xù)多次空間擴(kuò)展操作稱之為“連鎖更新”(cascade update)缺猛, 圖 7-13 展示了這一過程。

image.png

除了添加新節(jié)點(diǎn)可能會(huì)引發(fā)連鎖更新之外椭符, 刪除節(jié)點(diǎn)也可能會(huì)引發(fā)連鎖更新荔燎。

考慮圖 7-14 所示的壓縮列表, 如果 e1 至 eN 都是大小介于 250 字節(jié)至 253 字節(jié)的節(jié)點(diǎn)销钝, big 節(jié)點(diǎn)的長度大于等于 254 字節(jié)(需要 5 字節(jié)的 previous_entry_length 來保存)有咨, 而 small 節(jié)點(diǎn)的長度小于 254 字節(jié)(只需要 1 字節(jié)的 previous_entry_length 來保存), 那么當(dāng)我們將 small 節(jié)點(diǎn)從壓縮列表中刪除之后蒸健, 為了讓 e1 的 previous_entry_length 屬性可以記錄 big 節(jié)點(diǎn)的長度座享, 程序?qū)U(kuò)展 e1 的空間, 并由此引發(fā)之后的連鎖更新似忧。

image.png

因?yàn)檫B鎖更新在最壞情況下需要對(duì)壓縮列表執(zhí)行 N 次空間重分配操作渣叛, 而每次空間重分配的最壞復(fù)雜度為 O(N) , 所以連鎖更新的最壞復(fù)雜度為 O(N^2) 盯捌。

要注意的是淳衙, 盡管連鎖更新的復(fù)雜度較高, 但它真正造成性能問題的幾率是很低的:

  • 首先饺著, 壓縮列表里要恰好有多個(gè)連續(xù)的箫攀、長度介于 250 字節(jié)至 253 字節(jié)之間的節(jié)點(diǎn), 連鎖更新才有可能被引發(fā)幼衰, 在實(shí)際中靴跛, 這種情況并不多見;
  • 其次渡嚣, 即使出現(xiàn)連鎖更新汤求, 但只要被更新的節(jié)點(diǎn)數(shù)量不多俏险, 就不會(huì)對(duì)性能造成任何影響: 比如說, 對(duì)三五個(gè)節(jié)點(diǎn)進(jìn)行連鎖更新是絕對(duì)不會(huì)影響性能的扬绪;

因?yàn)橐陨显颍?ziplistPush 等命令的平均復(fù)雜度僅為 O(N) 竖独, 在實(shí)際中, 我們可以放心地使用這些函數(shù)挤牛, 而不必?fù)?dān)心連鎖更新會(huì)影響壓縮列表的性能莹痢。

重點(diǎn)回顧

  • 壓縮列表是一種為節(jié)約內(nèi)存而開發(fā)的順序型數(shù)據(jù)結(jié)構(gòu)。
  • 壓縮列表被用作列表鍵和哈希鍵的底層實(shí)現(xiàn)之一墓赴。
  • 壓縮列表可以包含多個(gè)節(jié)點(diǎn)竞膳,每個(gè)節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者整數(shù)值。
  • 添加新節(jié)點(diǎn)到壓縮列表诫硕, 或者從壓縮列表中刪除節(jié)點(diǎn)坦辟, 可能會(huì)引發(fā)連鎖更新操作, 但這種操作出現(xiàn)的幾率并不高章办。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锉走,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藕届,更是在濱河造成了極大的恐慌挪蹭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件休偶,死亡現(xiàn)場(chǎng)離奇詭異梁厉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)踏兜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門词顾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碱妆,你說我怎么就攤上這事计技。” “怎么了山橄?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵垮媒,是天一觀的道長。 經(jīng)常有香客問我航棱,道長睡雇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任饮醇,我火速辦了婚禮它抱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朴艰。我一直安慰自己观蓄,他們只是感情好混移,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著侮穿,像睡著了一般歌径。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亲茅,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天回铛,我揣著相機(jī)與錄音,去河邊找鬼克锣。 笑死茵肃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袭祟。 我是一名探鬼主播验残,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼巾乳!你這毒婦竟也來了您没?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤想鹰,失蹤者是張志新(化名)和其女友劉穎紊婉,沒想到半個(gè)月后药版,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辑舷,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年槽片,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了何缓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡还栓,死狀恐怖碌廓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剩盒,我是刑警寧澤谷婆,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站辽聊,受9級(jí)特大地震影響纪挎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜跟匆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一异袄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧玛臂,春花似錦烤蜕、人聲如沸封孙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虎忌。三九已至,卻和暖如春斑匪,著一層夾襖步出監(jiān)牢的瞬間呐籽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工蚀瘸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狡蝶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓贮勃,卻偏偏與公主長得像贪惹,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寂嘉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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

  • 壓縮列表(ziplist)是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一奏瞬。 7.1 壓縮列表的構(gòu)成 壓縮列表是Redis為了節(jié)約內(nèi)...
    豬大金閱讀 2,090評(píng)論 0 2
  • 壓縮列表時(shí)列表鍵和哈希鍵的底層實(shí)現(xiàn)之一。當(dāng)一個(gè)列表鍵只包含少量列表項(xiàng)泉孩,并且每個(gè)列表項(xiàng)都是小整數(shù)或者長度較短的字符串...
    杰哥長得帥閱讀 1,025評(píng)論 0 0
  • 壓縮列表是哈希鍵和列表鍵的底層實(shí)現(xiàn)之一硼端。當(dāng)一個(gè)列表鍵只包含少量的列表項(xiàng),并且每個(gè)列表項(xiàng)要么就是小整數(shù)值珍昨,要么就是長...
    Felicia1993閱讀 462評(píng)論 0 0
  • 壓縮列表(ziplist)是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一句喷。 當(dāng)一個(gè)列表鍵只包含少量列表項(xiàng)镣典, 并且每個(gè)列表項(xiàng)要么就是...
    顏灝_2181閱讀 331評(píng)論 0 0
  • 我早已厭倦這樣的游戲 無數(shù)次下定決心 無數(shù)次放棄 我以為換個(gè)地方就會(huì)有好的結(jié)局 但我又再次跌入谷底 我總在深夜流淚...
    PH小文閱讀 173評(píng)論 0 0