Redis筆記(四)- 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)_Zset

Redis中的zset,首先它是一個(gè)set,set中的元素具有不可重復(fù)性,其次它也是一個(gè)有序集合,其中的元素按照一定的評(píng)分進(jìn)行排序。Set的內(nèi)部結(jié)構(gòu)我們就不說(shuō)了,可以參考上一節(jié)的內(nèi)容《Redis筆記(三)- 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)_Hash和Set》,我們這次主要探討一下zset中元素是怎么實(shí)現(xiàn)有序的。

一般的zset使用這兩種結(jié)構(gòu)共同存儲(chǔ)元素.png

概括

實(shí)際上zset對(duì)于排序有兩種結(jié)構(gòu)實(shí)現(xiàn),一種是在元素個(gè)數(shù)比較少且總長(zhǎng)度比較短的時(shí)候使用壓縮列表(ziplist,參考《Redis筆記(二)- 基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)_List》中的部分內(nèi)容),另一種是不符合上述條件時(shí)使用跳躍列表(skiplist)。

ziplist使用條件:

  1. 元素?cái)?shù)量小于128個(gè)。
  2. 所有元素成員的長(zhǎng)度小于64字節(jié)。

zset中的壓縮列表ziplist

先來(lái)回顧一下ziplist的結(jié)構(gòu)

struct ziplist<T> {
    int32 zlbytes; // 整個(gè)壓縮列表占用字節(jié)數(shù)
    int32 zltail_offset; // 最后一個(gè)元素距離壓縮列表起始位置的偏移量,用于快速定位到最后一個(gè)節(jié)點(diǎn)
    int16 zllength; // 元素個(gè)數(shù)
    T[] entries; // 元素內(nèi)容列表,緊湊的連續(xù)內(nèi)存空間
    int8 zlend; // 標(biāo)志壓縮列表的結(jié)束,值恒為 0xFF
}

每一個(gè)entry的結(jié)構(gòu):

struct entry {
    int<var> prevlen; // 前一個(gè) entry 的字節(jié)長(zhǎng)度
    int<var> encoding; // 元素類型編碼
    optional byte[] content; // 元素內(nèi)容
}

zset中的ziplist元素是怎樣存的呢?entries中每?jī)蓚€(gè)連續(xù)entry為一組,表示一個(gè)元素,第一個(gè)entry保存的是其元素的值,第二個(gè)entry保存的是其評(píng)分,評(píng)分低的排在entries數(shù)組的前面,評(píng)分高的排在entries數(shù)組的后面。

zset中的跳躍列表skiplist

先來(lái)張圖解釋下跳躍列表:

跳躍列表

最底層是一個(gè)有序的鏈表,查找一個(gè)元素,需要O(n)的時(shí)間復(fù)雜度,但是如果從這個(gè)鏈表抽出一部分元素,形成一個(gè)新的鏈表,把這個(gè)鏈表當(dāng)做粗略的索引,遍歷索引的次數(shù)要比遍歷原始鏈表的次數(shù)大大降低。典型的空間換時(shí)間的思維。

檢索過(guò)程

舉個(gè)例子,如果我要查找"13"這個(gè)元素,首先從最高的第二級(jí)索引層遍歷,當(dāng)遍歷過(guò)元素"7"之后,發(fā)現(xiàn)我要找的"13"這個(gè)元素是小于下一個(gè)元素"14"的,因此,進(jìn)入第一級(jí)索引層繼續(xù)從"7"遍歷到"14",當(dāng)遍歷過(guò)元素"10"之后,要找的"13"這個(gè)元素還是小于下一個(gè)元素"14"的,于是繼續(xù)在下一級(jí)的原始鏈表中從"10"遍歷到"14",最后就能在這個(gè)范圍中找到"13"這個(gè)元素。


遍歷過(guò)程,時(shí)間復(fù)雜度O(lg(n))
插入過(guò)程

redis的跳躍列表并不是按照嚴(yán)格的"上一層的索引節(jié)點(diǎn)數(shù)是下一層的二分之一"這樣的規(guī)則進(jìn)行構(gòu)造的,因?yàn)榧偃绨凑者@個(gè)規(guī)則進(jìn)行插入,則插入元素的左右節(jié)點(diǎn)的層數(shù)都要調(diào)整。而實(shí)際上每插入一個(gè)元素,這個(gè)元素所擁有的層數(shù)是隨機(jī)的。看個(gè)插入過(guò)程的例子:


插入

每插入一個(gè)元素隨機(jī)層數(shù)的計(jì)算方法如下:

  • 首先,每個(gè)節(jié)點(diǎn)肯定都有第1層指針(每個(gè)節(jié)點(diǎn)都在第1層鏈表里)。
  • 如果一個(gè)節(jié)點(diǎn)有第i層(i>=1)指針(即節(jié)點(diǎn)已經(jīng)在第1層到第i層鏈表中),那么它有第(i+1)層指針的概率為p^(i-1),p默認(rèn)1/4。
  • 節(jié)點(diǎn)最大的層數(shù)不允許超過(guò)一個(gè)最大值,記為MaxLevel。

總結(jié)

  1. 查詢?cè)氐姆謹(jǐn)?shù)值,是用zset中的hash結(jié)構(gòu)來(lái)查的。
  2. 范圍查詢或者獲取元素的排名是根據(jù)skiplist來(lái)查的。這里skiplist做了一點(diǎn)小優(yōu)化,就是在元素的每層索引的指針擴(kuò)展了一個(gè)屬性,這個(gè)屬性存儲(chǔ)了這個(gè)元素到指針指向的下一個(gè)元素之前有多少個(gè)節(jié)點(diǎn),又稱跨度,這樣在檢索一個(gè)元素的時(shí)候就可以很方便的知道在這個(gè)元素在整個(gè)鏈表的排名。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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