前言

  • 在之前我們已經學習了redis五大資料結構中的list結構。其內部是linkedList和zipList兩種結構。這是我們已經學習的內容。之前我沒有結合操作具體檢視。事實上在兩者中還存在一種結合體quickList

結構演變

  • 在上面我們添加了一個key為zlist的資料。通過object encoding zlist檢視底層就是通過quicklist來構建的。之前在ziplist章節彙總我們瞭解到在redis中hash和list基本資料結構都使用了ziplist儲存資料的。在list中我們確實quicklist。這裡我們提前說明下quicklist內部就是基於ziplist來實現的。

linkedList

  • 在開場quicklist之前我們簡單梳理下之前學過的linkedList ,他是一種常見的雙線連結串列。通過兩個指標完成我們連結串列的構建。

C++指標

  • redis是基於記憶體執行的,而記憶體有十分的寶貴所以redis在設計了雙線連結串列後覺得有點耗記憶體。因為指標本身也是需要開闢空間的。根據系統的不同指標佔位不同。這裡我總結了一下一個指標佔位就是一個系統操作的基本位
  • 這裡基本位是什麼意思呢?加入你是64位系統那麼一個指標就是64位即8個位元組。如果你是32位系統那麼一個指標就是32位即4個位元組
  • 也就是說如果我在redis中向雙向連結串列中儲存N個英文字母,我們又知道一個應為字母佔1個位元組。那麼這N個元素就是N的listNode . 那麼維持著N個listNode中間就需要2*(N-1)個指標。在64位系統中也就是我們需要開闢將近129倍的空間來儲存內容。上述情況我們只有N個位元組的內容,卻需要2*(N-1)*8+N個位元組來構建listNode。
  • 隨著節點的遞增我們浪費程度越離譜。所以redis在雙向連結串列的基礎上結合了ziplist進行改良。

過渡原因

ziplist

  • 在ziplist章節中我們知道ziplist是一塊連續記憶體,是redis對記憶體的一種改良結構。ziplist實現了記憶體的高使用率!

linkedlist+ziplist好處

quicklist引入

  • quicklist是在redis3.2之後引入的,筆者這裡使用的是redis6.4方便原始碼好像並沒有quicklist原始碼。
  • 後來翻閱了之後redis6.4好像取消了quicklist . 結構。所以我又特別下了一個3.2的版本。這裡具體的是redis3.2.4版本!!!

廬山真面目

quicklist

  • 通過他的原始碼我們很清晰的看出他的內部資料結構!這個大家應該很熟悉了。quicklist可以說就是我們之前的linkedList 結構。內部就是雙向連結串列只不過裡面的屬性稍微多了點

  • 通過圖示是不是感覺和linkedList一樣。

  • 接下來我們看看quicklist中各個屬性的含義吧

quicklistNode

  • quicklist只是一個抽象的概念,真正負責資料的儲存的是組成quicklist的成員quicklistNode 。

  • 各個屬性的作用

  • 通過上面的屬性介紹,我們也可以瞭解瞭解到node節點中的資料結構就是ziplist 。在ziplist基礎上會在進行壓縮達到記憶體更高的使用效率!

  • 關於壓縮這裡我們不用太去了解!主要目的就是一種編碼,這種編碼是無法真正使用的在使用期間redis會進行解碼操作。在解碼操作期間就是通過recompress屬性來標記的。

insert

  • 在瞭解quicklist基本結構之後我們在看看insert時結構會發生哪些變化!上面我們也提到了在redis.conf配置檔案中list-max-ziplist-size屬性是用來設定quicklist中每個節點中的ziplist儲存的大小設定的。
屬性值 作用
-1 每個quicklistNode節點的ziplist所佔位元組數不能超過4kb
-2 每個quicklistNode節點的ziplist所佔位元組數不能超過8kb
-3 每個quicklistNode節點的ziplist所佔位元組數不能超過16kb
-4 每個quicklistNode節點的ziplist所佔位元組數不能超過32kb
-5 每個quicklistNode節點的ziplist所佔位元組數不能超過64kb
int ziplist包含的entry上限

兩端插入

  • 第一種情況就是我們需要插入的資料是在兩端的。如上圖所示我們在redis.conf配置檔案中設定的list-max-ziplist-size: 2 。表示內部節點ziplist中entry個數最大為2 。此時我們head頭部節點中已經儲存了兩個內容,tail尾部節點儲存的是1個節點!
  • 這個時候如果我們想頭部新增一個元素是obj1 。 可想而知我們是無法加入的,這個時候redis會重新建立一個ziplist結構幷包含obj1 ,將新建立的ziplist加入到連結串列的頭部之後

  • 而obj2加入尾結點時,因為尾結點的節點數是1還未達到峰值2,所以直接就加入了。最終的效果圖如下

中間插入

st=>start: Insert
ziplistInsert=>operation: 向ziplist中插入
subziplistInsert=>operation: 將該ziplist拆分兩個ziplist, 在對應位置加入
insertNear=>operation: 插入相鄰的ziplist中
newZipInsert=>operation: 新建ziplist插入
cond=>condition: ziplist是否可以容納
headtailCond=>condition: 插入位置在ziplist兩端
nearheadtailCond=>condition: 相鄰ziplist是否可以容納
e=>end: 快樂的一天 st->cond
cond(yes)->ziplistInsert
cond(no)->headtailCond(yes)->nearheadtailCond
headtailCond(no)->subziplistInsert
nearheadtailCond(yes)->insertNear
nearheadtailCond(no)->newZipInsert

總結

參考文獻

lzf壓縮演算法