不是吧?為了加快速度泳猬,Redis竟做了這么“瘋狂”的設(shè)計

前言

列表對象是 Redis 中 5 種基礎(chǔ)數(shù)據(jù)類型之一得封,在 Redis 3.2 版本之前,列表對象底層存儲結(jié)構(gòu)有兩種:linkedlist(雙端列表)和 ziplist(壓縮列表)拷呆,而在 Redis 3.2 版本之后疫粥,列表對象底層存儲結(jié)構(gòu)只有一種:quicklist(快速列表)梗逮,難道通過精心設(shè)計的 ziplist 最終被 Redis 拋棄了嗎?

列表對象

同字符串對象一樣娄蔼,列表對象到底使用哪一種數(shù)據(jù)結(jié)構(gòu)來進(jìn)行存儲也是通過編碼來進(jìn)行區(qū)分:

編碼屬性描述object encoding命令返回值OBJ_ENCODING_LINKEDLIST使用 linkedlist 實現(xiàn)列表對象linkedlistOBJ_ENCODING_ZIPLIST使用 ziplist 實現(xiàn)列表對象ziplistOBJ_ENCODING_QUICKLIST使用 quicklist 實現(xiàn)列表對象quicklist

linkedlist

linkedlist 是一個雙向列表岁诉,每個節(jié)點都會存儲指向上一個節(jié)點和指向下一個節(jié)點的指針跋选。linkedlist 因為每個節(jié)點之間的空間是不連續(xù)的野建,所以可能會造成過多的內(nèi)存空間碎片。

linkedlist存儲結(jié)構(gòu)

鏈表中每一個節(jié)點都是一個 listNode 對象(源碼 adlist.h 內(nèi))同眯,不過需要注意的是唯鸭,列表中的 value 其實也是一個字符串對象目溉,其他幾種數(shù)據(jù)類型其內(nèi)部最終也是會嵌套字符串對象,字符串對象也是唯一一種會被其他對象引用的基本類型:

typedefstructlistNode{

structlistNode*prev;//前一個節(jié)點

structlistNode*next;//后一個節(jié)點

void*value;//值(字符串對象)

} listNode;

然后會將其再進(jìn)行封裝成為一個 list 對象(源碼 adlist.h 內(nèi)):

typedefstructlist{

listNode *head;//頭節(jié)點

listNode *tail;//尾節(jié)點

void*(*dup)(void*ptr);//節(jié)點值復(fù)制函數(shù)

void(*free)(void*ptr);//節(jié)點值釋放函數(shù)

int(*match)(void*ptr,void*key);//節(jié)點值對比函數(shù)

unsignedlonglen;//節(jié)點數(shù)量

}list;

Redis 中對 linkedlist 的訪問是以 NULL 值為終點的,因為 head 節(jié)點的 prev 節(jié)點為 NULL陷猫,tail 節(jié)點的 next 節(jié)點也為 NULL绣檬,所以從頭節(jié)點開始遍歷,當(dāng)發(fā)現(xiàn) tail 為 NULL 時墨缘,則可以認(rèn)為已經(jīng)到了列表末尾零抬。

當(dāng)我們設(shè)置一個列表對象時平夜,在 Redis 3.2 版本之前我們可以得到如下存儲示意圖:


ziplist

壓縮列表在前面已經(jīng)介紹過褥芒,想要詳細(xì)了解的可以點擊這里。

linkedlist 和 ziplist 的選擇

在 Redis3.2 之前献酗,linkedlist 和 ziplist 兩種編碼可以進(jìn)選擇切換坷牛,如果需要列表使用 ziplist 編碼進(jìn)行存儲京闰,則必須滿足以下兩個條件:

列表對象保存的所有字符串元素的長度都小于 64 字節(jié)甩苛。

列表對象保存的元素數(shù)量小于 512 個讯蒲。

一旦不滿足這兩個條件的任意一個肄扎,則會使用 linkedlist 編碼進(jìn)行存儲犯祠。

PS:這兩個條件可以通過參數(shù) list-max-ziplist-value 和 list-max-ziplist-entries 進(jìn)行修改。

這兩種列表能在特定的場景下發(fā)揮各自的作用搔耕,應(yīng)該來說已經(jīng)能滿足大部分需求了度迂,然后 Redis 并不滿足于此猜揪,于是一場改革引發(fā)了而姐,quicklist 橫空出世。

quicklist

在 Redis 3.2 版本之后钧萍,為了進(jìn)一步提升 Redis 的性能政鼠,列表對象統(tǒng)一采用 quicklist 來存儲列表對象公般。quicklist存儲了一個雙向列表,每個列表的節(jié)點是一個 ziplist瞬雹,所以實際上 quicklist 并不是一個新的數(shù)據(jù)結(jié)構(gòu)刽虹,它就是linkedlist 和 ziplist 的結(jié)合,然后被命名為快速列表尚镰。

quicklist 內(nèi)部存儲結(jié)構(gòu)

quicklist 中每一個節(jié)點都是一個 quicklistNode 對象哪廓,其數(shù)據(jù)結(jié)構(gòu)定義如下:

typedefstructquicklistNode{

structquicklistNode*prev;//前一個節(jié)點

structquicklistNode*next;//后一個節(jié)點

unsignedchar*zl;//當(dāng)前指向的ziplist或者quicklistLZF

unsignedintsz;//當(dāng)前ziplist占用字節(jié)

unsignedintcount :16;//ziplist中存儲的元素個數(shù)撩独,16字節(jié)(最大65535個)

unsignedintencoding :2;//是否采用了LZF壓縮算法壓縮節(jié)點 1:RAW 2:LZF

unsignedintcontainer :2;//存儲結(jié)構(gòu)账月,NONE=1, ZIPLIST=2

unsignedintrecompress :1;//當(dāng)前ziplist是否需要再次壓縮(如果前面被解壓過則為true局齿,表示需要再次被壓縮)

unsignedintattempted_compress :1;//測試用

unsignedintextra :10;//后期留用

} quicklistNode;

然后各個 quicklistNode 就構(gòu)成了一個快速列表 quicklist:

typedefstructquicklist{

quicklistNode *head;//列表頭節(jié)點

quicklistNode *tail;//列表尾節(jié)點

unsignedlongcount;//ziplist中一共存儲了多少元素抓歼,即:每一個quicklistNode內(nèi)的count相加

unsignedlonglen;//雙向鏈表的長度,即quicklistNode的數(shù)量

intfill :16;//填充因子

unsignedintcompress :16;//壓縮深度 0-不壓縮

} quicklist;

根據(jù)這兩個結(jié)構(gòu)萄喳,我們可以得到 Redis 3.2 版本之后的列表對象的一個存儲結(jié)構(gòu)示意圖:


quicklist 的 compress 屬性

compress 是用來表示壓縮深度他巨,ziplist 除了內(nèi)存空間是連續(xù)之外染突,還可以采用特定的 LZF 壓縮算法來將節(jié)點進(jìn)行壓縮存儲辈灼,從而更進(jìn)一步的節(jié)省空間份企,壓縮深度可以通過參數(shù) list-compress-depth 控制:

0:不壓縮(默認(rèn)值)

1:首尾第1個元素不壓縮

2:首位前2個元素不壓縮

3:首尾前3個元素不壓縮

以此類推

注意:之所以采取這種壓縮兩端節(jié)點的方式是因為很多場景都是兩端的元素訪問率最高的,而中間元素訪問率相對較低巡莹,所以在實際使用時司志,我們可以根據(jù)自己的實際情況選擇是否進(jìn)行壓縮,以及具體的壓縮深度降宅。

quicklistNode 的 zl 指針

zl 指針默認(rèn)指向了 ziplist俐芯,上面提到 quicklistNode 中有一個 sz 屬性記錄了當(dāng)前 ziplist 占用的字節(jié),不過這僅僅限于當(dāng)前節(jié)點沒有被壓縮(通過LZF 壓縮算法)的情況钉鸯,如果當(dāng)前節(jié)點被壓縮了吧史,那么被壓縮節(jié)點的 zl 指針會指向另一個對象 quicklistLZF,而不會直接指向 ziplist。quicklistLZF 是一個 4+N 字節(jié)的結(jié)構(gòu):

typedefstructquicklistLZF{

unsignedintsz;// LZF大小吨述,占用4字節(jié)

charcompressed[];//被壓縮的內(nèi)容,占用N字節(jié)

} quicklistLZF;

quicklist 對比原始兩種編碼的改進(jìn)

quicklist 同樣采用了 linkedlist 的雙端列表特性钞脂,然后 quicklist 中的每個節(jié)點又是一個 ziplist揣云,所以quicklist 就是綜合平衡考慮了 linkedlist 容易產(chǎn)生空間碎片的問題和 ziplist 的讀寫性能兩個維度而設(shè)計出來的一種數(shù)據(jù)結(jié)構(gòu)。使用 quicklist 需要注意以下 2 點:

如果 ziplist 中的 entry 個數(shù)過少冰啃,最極端情況就是只有 1 個 entry 的壓縮列表邓夕,那么此時 quicklist 就相當(dāng)于退化成了一個普通的 linkedlist。

如果 ziplist 中的 entry 過多阎毅,那么也會導(dǎo)致一次性需要申請的內(nèi)存空間過大(ziplist 空間是連續(xù)的)焚刚,而且因為 ziplist 本身的就是以時間換空間,所以會過多 entry 也會影響到列表對象的讀寫性能扇调。

ziplist 中的 entry 個數(shù)可以通過參數(shù) list-max-ziplist-size 來控制:

list-max-ziplist-size1

注意:這個參數(shù)可以配置正數(shù)也可以配置負(fù)數(shù)矿咕。正數(shù)表示限制每個節(jié)點中的 entry 數(shù)量,如果是負(fù)數(shù)則只能為 -1~-5狼钮,其代表的含義如下:

-1:每個 ziplist 最多只能為 4KB

-2:每個 ziplist 最多只能為 8KB

-3:每個 ziplist 最多只能為 16KB

-4:每個 ziplist 最多只能為 32KB

-5:每個 ziplist 最多只能為 64KB

列表對象常用操作命令

lpush key value1 value2:將一個或者多個 value 插入到列表 key 的頭部碳柱,key 不存在則創(chuàng)建 key(value2 在value1 之后)。

lpushx key value1 value2:將一個或者多個 value 插入到列表 key 的頭部熬芜,key 不存在則不做任何處理(value2 在value1 之后)莲镣。

lpop key:移除并返回 key 值的列表頭元素。

rpush key value1 value2:將一個或者多個 value 插入到列表 key 的尾部涎拉,key 不存在則創(chuàng)建 key(value2 在value1 之后)剥悟。

rpushx key value1 vaue2:將一個或者多個 value 插入到列表 key 的尾部,key 不存在則不做任何處理(value2 在value1 之后)曼库。

rpop key:移除并返回列表 key 的尾元素区岗。

llen key:返回列表 key 的長度。

lindex key index:返回列表 key 中下標(biāo)為 index 的元素毁枯。index 為正數(shù)(從 0 開始)表示從隊頭開始算慈缔,index 為負(fù)數(shù)(從-1開始)則表示從隊尾開始算。

lrange key start stop:返回列表 key 中下標(biāo) [start,end] 之間的元素种玛。

lset key index value:將 value 設(shè)置到列表 key 中指定 index 位置藐鹤,key 不存在或者 index 超出范圍則會報錯。

ltrim key start end:截取列表中 [start,end] 之間的元素赂韵,并替換原列表保存娱节。

了解了操作列表對象的常用命令,我們就可以來驗證下前面提到的列表對象的類型和編碼了祭示,在測試之前為了防止其他 key 值的干擾肄满,我們先執(zhí)行 flushall 命令清空 Redis 數(shù)據(jù)庫。

接下來依次輸入命令:

lpush name zhangsan

type name

object encoding name


可以看到,通過 type 命令輸出的是 list稠歉,說明當(dāng)前 name 存的是一個列表對象掰担,并且編碼是 quicklist(示例中用的是 5.0.5 版本)。

總結(jié)

本文主要介紹了 Redis 中 5 種常用數(shù)據(jù)類型中的 列表對象怒炸,并介紹了底層的存儲結(jié)構(gòu) quicklist带饱,并分別對舊版本的兩種底層數(shù)據(jù) linkedlist 和 ziplist 進(jìn)行了分析對比得出了為什么 Redis 最終要采用 quicklist 來存儲列表對象。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阅羹,一起剝皮案震驚了整個濱河市勺疼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捏鱼,老刑警劉巖执庐,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異穷躁,居然都是意外死亡耕肩,警方通過查閱死者的電腦和手機(jī)因妇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門问潭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人婚被,你說我怎么就攤上這事狡忙。” “怎么了址芯?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵灾茁,是天一觀的道長。 經(jīng)常有香客問我谷炸,道長北专,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任旬陡,我火速辦了婚禮拓颓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘描孟。我一直安慰自己驶睦,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布匿醒。 她就那樣靜靜地躺著场航,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廉羔。 梳的紋絲不亂的頭發(fā)上溉痢,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼适室。 笑死嫡意,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捣辆。 我是一名探鬼主播蔬螟,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汽畴!你這毒婦竟也來了旧巾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤忍些,失蹤者是張志新(化名)和其女友劉穎鲁猩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罢坝,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡廓握,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘁酿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隙券。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖闹司,靈堂內(nèi)的尸體忽然破棺而出娱仔,到底是詐尸還是另有隱情,我是刑警寧澤游桩,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布牲迫,位于F島的核電站,受9級特大地震影響借卧,放射性物質(zhì)發(fā)生泄漏盹憎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一铐刘、第九天 我趴在偏房一處隱蔽的房頂上張望陪每。 院中可真熱鬧,春花似錦滨达、人聲如沸奶稠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锌订。三九已至,卻和暖如春画株,著一層夾襖步出監(jiān)牢的瞬間辆飘,已是汗流浹背啦辐。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蜈项,地道東北人芹关。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像紧卒,于是被迫代替她去往敵國和親侥衬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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

  • Redis 是速度非常快的非關(guān)系型(NoSQL)內(nèi)存鍵值數(shù)據(jù)庫博个,可以存儲鍵和五種不同類型的值之間的映射怀樟。 Redi...
    DevilRoshan閱讀 241評論 0 2
  • Redis主要支持的數(shù)據(jù)類型有5種:String ,Hash 盆佣,List 往堡,Set ,和 Sorted Set共耍。 ...
    愛情小傻蛋閱讀 1,403評論 0 0
  • 推薦指數(shù): 6.0 書籍主旨關(guān)鍵詞:特權(quán)虑灰、焦點、注意力征堪、語言聯(lián)想瘩缆、情景聯(lián)想 觀點: 1.統(tǒng)計學(xué)現(xiàn)在叫數(shù)據(jù)分析关拒,社會...
    Jenaral閱讀 5,701評論 0 5
  • 昨天佃蚜,在回家的路上,坐在車?yán)镉圃沼圃盏乜粗摹度龉衬墓适隆纷虐恚冶焕锩娴膬?nèi)容深深吸引住了谐算,盡管上學(xué)時...
    夜闌曉語閱讀 3,778評論 2 9
  • 一。匹配归露。 判斷一個字符串是否符合我們制定的規(guī)則洲脂? 二…捕獲 字符串中符合我們正則表達(dá)式,規(guī)則的剧包,內(nèi)容捕獲到恐锦。 三...
    時修七年閱讀 973評論 2 0