溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的

發(fā)布時(shí)間:2021-11-15 15:53:08 來源:億速云 閱讀:156 作者:柒染 欄目:大數(shù)據(jù)

本篇文章給大家分享的是有關(guān)Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

一、前言

Redis 提供了5種數(shù)據(jù)類型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每種數(shù)據(jù)類型的特點(diǎn)對于redis的開發(fā)和運(yùn)維非常重要。

<p align="right"><a href="http://www.yund.tech/zdetail.html?type=1&id=73b09694e9867b5808a4d8706f2f51b5">原文解析</a></p>

Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的

二、命令實(shí)現(xiàn)

?因?yàn)橛行蚣湘I的值為有序集合對象,所以用于有序集合鍵的所有命令都是針對有序集合對象來構(gòu)建的。

命令ziplist 編碼的實(shí)現(xiàn)方法zset 編碼的實(shí)現(xiàn)方法
ZADD調(diào)用 ziplistInsert 函數(shù), 將成員和分值作為兩個節(jié)點(diǎn)分別插入到壓縮列表。先調(diào)用 zslInsert 函數(shù), 將新元素添加到跳躍表, 然后調(diào)用 dictAdd 函數(shù), 將新元素關(guān)聯(lián)到字典。
ZCARD調(diào)用 ziplistLen 函數(shù), 獲得壓縮列表包含節(jié)點(diǎn)的數(shù)量, 將這個數(shù)量除以 2 得出集合元素的數(shù)量。訪問跳躍表數(shù)據(jù)結(jié)構(gòu)的 length 屬性, 直接返回集合元素的數(shù)量。
ZCOUNT遍歷壓縮列表, 統(tǒng)計(jì)分值在給定范圍內(nèi)的節(jié)點(diǎn)的數(shù)量。遍歷跳躍表, 統(tǒng)計(jì)分值在給定范圍內(nèi)的節(jié)點(diǎn)的數(shù)量。
ZRANGE從表頭向表尾遍歷壓縮列表, 返回給定索引范圍內(nèi)的所有元素。從表頭向表尾遍歷跳躍表, 返回給定索引范圍內(nèi)的所有元素。
ZREVRANGE從表尾向表頭遍歷壓縮列表, 返回給定索引范圍內(nèi)的所有元素。從表尾向表頭遍歷跳躍表, 返回給定索引范圍內(nèi)的所有元素。
ZRANK從表頭向表尾遍歷壓縮列表, 查找給定的成員, 沿途記錄經(jīng)過節(jié)點(diǎn)的數(shù)量, 當(dāng)找到給定成員之后, 途經(jīng)節(jié)點(diǎn)的數(shù)量就是該成員所對應(yīng)元素的排名。從表頭向表尾遍歷跳躍表, 查找給定的成員, 沿途記錄經(jīng)過節(jié)點(diǎn)的數(shù)量, 當(dāng)找到給定成員之后, 途經(jīng)節(jié)點(diǎn)的數(shù)量就是該成員所對應(yīng)元素的排名。
ZREVRANK從表尾向表頭遍歷壓縮列表, 查找給定的成員, 沿途記錄經(jīng)過節(jié)點(diǎn)的數(shù)量, 當(dāng)找到給定成員之后, 途經(jīng)節(jié)點(diǎn)的數(shù)量就是該成員所對應(yīng)元素的排名。從表尾向表頭遍歷跳躍表, 查找給定的成員, 沿途記錄經(jīng)過節(jié)點(diǎn)的數(shù)量, 當(dāng)找到給定成員之后, 途經(jīng)節(jié)點(diǎn)的數(shù)量就是該成員所對應(yīng)元素的排名。
ZREM遍歷壓縮列表, 刪除所有包含給定成員的節(jié)點(diǎn), 以及被刪除成員節(jié)點(diǎn)旁邊的分值節(jié)點(diǎn)。遍歷跳躍表, 刪除所有包含了給定成員的跳躍表節(jié)點(diǎn)。 并在字典中解除被刪除元素的成員和分值的關(guān)聯(lián)。
ZSCORE遍歷壓縮列表, 查找包含了給定成員的節(jié)點(diǎn), 然后取出成員節(jié)點(diǎn)旁邊的分值節(jié)點(diǎn)保存的元素分值。直接從字典中取出給定成員的分值。

三、結(jié)構(gòu)解析

?由前文和上圖可知,有序集合的編碼可以是 ziplist 或者 skiplist 。ziplist 編碼的有序集合對象使用壓縮列表作為底層實(shí)現(xiàn), 每個集合元素使用兩個緊挨在一起的壓縮列表節(jié)點(diǎn)來保存, 第一個節(jié)點(diǎn)保存元素的成員(member), 而第二個元素則保存元素的分值(score)。

壓縮列表方式

壓縮列表內(nèi)的集合元素按分值從小到大進(jìn)行排序, 分值較小的元素被放置在靠近表頭的方向, 而分值較大的元素則被放置在靠近表尾的方向。

例如,如果我們執(zhí)行以下 ZADD 命令, 那么服務(wù)器將創(chuàng)建一個有序集合對象作為 price 鍵的值:

redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3

如果 price 鍵的值對象使用的是 ziplist 編碼, 那么這個值對象將會是圖 8-14 所示,, 而對象所使用的壓縮列表則會是 8-15 所示。 <center>Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的</center><center>Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的</center>

跳躍表和字典方式

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

typedef struct zset {

    zskiplist *zsl;

    dict *dict;

} zset;

zset 結(jié)構(gòu)中的 zsl 跳躍表按分值從小到大保存了所有集合元素, 每個跳躍表節(jié)點(diǎn)都保存了一個集合元素: 跳躍表節(jié)點(diǎn)的 object 屬性保存了元素的成員, 而跳躍表節(jié)點(diǎn)的 score 屬性則保存了元素的分值。 通過這個跳躍表, 程序可以對有序集合進(jìn)行范圍型操作, 比如 ZRANK 、ZRANGE 等命令就是基于跳躍表 API 來實(shí)現(xiàn)的。

除此之外, zset 結(jié)構(gòu)中的 dict 字典為有序集合創(chuàng)建了一個從成員到分值的映射, 字典中的每個鍵值對都保存了一個集合元素: 字典的鍵保存了元素的成員, 而字典的值則保存了元素的分值。 通過這個字典, 程序可以用 O(1) 復(fù)雜度查找給定成員的分值, ZSCORE 命令就是根據(jù)這一特性實(shí)現(xiàn)的, 而很多其他有序集合命令都在實(shí)現(xiàn)的內(nèi)部用到了這一特性。

有序集合每個元素的成員都是一個字符串對象, 而每個元素的分值都是一個 double 類型的浮點(diǎn)數(shù)。 值得一提的是, 雖然 zset 結(jié)構(gòu)同時(shí)使用跳躍表和字典來保存有序集合元素, 但這兩種數(shù)據(jù)結(jié)構(gòu)都會通過指針來共享相同元素的成員和分值, 所以同時(shí)使用跳躍表和字典來保存集合元素不會產(chǎn)生任何重復(fù)成員或者分值, 也不會因此而浪費(fèi)額外的內(nèi)存

skiplist 編碼的有序集合對象, 那么這個有序集合對象將會是圖 8-16 所示, 而對象所使用的 zset 結(jié)構(gòu)將會是圖 8-17 所示。<center>Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的</center><center>Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的</center>

注意: ??為了展示方便, 圖 8-17 在字典和跳躍表中重復(fù)展示了各個元素的成員和分值, 但在實(shí)際中, 字典和跳躍表會共享元素的成員和分值, 所以并不會造成任何數(shù)據(jù)重復(fù), 也不會因此而浪費(fèi)任何內(nèi)存。

四、編碼轉(zhuǎn)換

當(dāng)有序集合對象可以同時(shí)滿足以下兩個條件時(shí), 對象使用 ziplist 編碼:

1.有序集合保存的元素?cái)?shù)量小于 128 個;

2.有序集合保存的所有元素成員的長度都小于 64 字節(jié);

不能滿足以上兩個條件的有序集合對象將使用 skiplist 編碼。

注意: ??以上兩個條件的上限值是可以修改的, 具體請看配置文件中關(guān)于 zset-max-ziplist-entries 選項(xiàng)和 zset-max-ziplist-value 選項(xiàng)的說明。對于使用 ziplist 編碼的有序集合對象來說, 當(dāng)使用 ziplist 編碼所需的兩個條件中的任意一個不能被滿足時(shí), 程序就會執(zhí)行編碼轉(zhuǎn)換操作, 將原本儲存在壓縮列表里面的所有集合元素轉(zhuǎn)移到 zset 結(jié)構(gòu)里面, 并將對象的編碼從 ziplist 改為 skiplist 。

1.以下情況展示了有序集合對象因?yàn)榘诉^多元素而引發(fā)編碼轉(zhuǎn)換:
# 對象包含了 128 個元素
redis> EVAL "for i=1, 128 do redis.call('ZADD', KEYS[1], i, i) end" 1 numbers
(nil)

redis> ZCARD numbers
(integer) 128

redis> OBJECT ENCODING numbers
"ziplist"

# 再添加一個新元素
redis> ZADD numbers 3.14 pi
(integer) 1

# 對象包含的元素?cái)?shù)量變?yōu)?nbsp;129 個
redis> ZCARD numbers
(integer) 129

# 編碼已改變
redis> OBJECT ENCODING numbers
"skiplist"
2.以下情況展示了有序集合對象因?yàn)樵氐某蓡T過長而引發(fā)編碼轉(zhuǎn)換:
# 向有序集合添加一個成員只有三字節(jié)長的元素
redis> ZADD blah 1.0 www
(integer) 1

redis> OBJECT ENCODING blah
"ziplist"

# 向有序集合添加一個成員為 66 字節(jié)長的元素
redis> ZADD blah 2.0 oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
(integer) 1

# 編碼已改變
redis> OBJECT ENCODING blah
"skiplist"

五、要點(diǎn)總結(jié)

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

2)一個 zset 結(jié)構(gòu)同時(shí)包含一個字典和一個跳躍表。

3)zset 結(jié)構(gòu)跳躍表和字典通過指針來共享相同元素的成員和分值。

4)有序集合對象 ziplist 或者 skiplist編碼,符合條件時(shí)可發(fā)生編碼轉(zhuǎn)換。

以上就是Redis 有序集合對象底層實(shí)現(xiàn)是怎樣的,小編相信有部分知識點(diǎn)可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI