天天看點

【Redis】資料結構與對象(六種底層資料結構)——壓縮連結清單

壓縮清單

壓縮清單(ziplist)是為了節約記憶體而設計的,是由一系列特殊編碼的連續記憶體塊組成的順序性(sequential)資料結構,一個壓縮清單可以包含多個節點,每個節點可以儲存一個位元組數組或者一個整數值

壓縮清單是清單(List)和散列(Hash)的底層實作之一,一個清單隻包含少量清單項,并且每個清單項是小整數值或比較短的字元串,會使用壓縮清單作為底層實作(在3.2版本之後是使用

quicklist

實作)

壓縮清單的構成

一個壓縮清單可以包含多個節點(entry),每個節點可以儲存一個位元組數組或者一個整數值

【Redis】資料結構與對象(六種底層資料結構)——壓縮連結清單

各部分組成說明如下

  • zlbytes

    :記錄整個壓縮清單占用的記憶體位元組數,在壓縮清單記憶體重配置設定,或者計算

    zlend

    的位置時使用
  • zltail

    :記錄壓縮清單表尾節點距離壓縮清單的起始位址有多少位元組,通過該偏移量,可以不用周遊整個壓縮清單就可以确定表尾節點的位址
  • zllen

    :記錄壓縮清單包含的節點數量,但該屬性值小于UINT16_MAX(65535)時,該值就是壓縮清單的節點數量,否則需要周遊整個壓縮清單才能計算出真實的節點數量
  • entryX

    :壓縮清單的節點
  • zlend

    :特殊值0xFF(十進制255),用于标記壓縮清單的末端

壓縮清單節點的構成

每個壓縮清單節點可以儲存一個位元組數字或者一個整數值,結構如下

【Redis】資料結構與對象(六種底層資料結構)——壓縮連結清單
  • previous_entry_ength

    :記錄壓縮清單前一個位元組的長度
  • encoding

    :節點的encoding儲存的是節點的content的内容類型
  • content

    :content區域用于儲存節點的内容,節點内容類型和長度由encoding決定

新增節點

在清單鍵的内容比較少時才會使用壓縮清單,那麼壓縮清單為什麼不能用于大的清單鍵呢?

ziplist 是連續存儲的資料結構,記憶體是沒有備援的(前面的文章講過的 SDS 中就有備援空間), 也就是說,每一次新增節點,都需要進行記憶體申請,然後将如果目前記憶體連續塊夠用,那麼将新節點添加,如果申請到的是另外一塊連續記憶體空間,那麼需要将所有的内容拷貝到新的位址。

也就是說,每一次新增節點,都需要記憶體配置設定,可能還需要進行記憶體拷貝。當 ziplist 中存儲的值太多,記憶體拷貝将是一個很大的消耗。

也是是以,Redis 隻在一些資料量小的場景下使用 ziplist.

級聯更新

再次看看

entry

元素的結構,有一個

previous_entry_length

字段,他的長度要麼都是1個位元組,要麼都是5個位元組:

  • 前一節點的長度小于254位元組,則

    previous_entry_length

    長度為1位元組
  • 前一節點的長度大于254位元組,則

    previous_entry_length

    長度為5位元組

假設現在存在一組壓縮清單,長度都在250位元組至253位元組之間,突然新增一新節點

new

, 長度大于等于254位元組,會出現:

【Redis】資料結構與對象(六種底層資料結構)——壓縮連結清單

程式需要不斷的對壓縮清單進行空間重配置設定工作,直到結束。

除了增加操作,删除操作也有可能帶來“連鎖更新”。 請看下圖,ziplist中所有entry節點的長度都在250位元組至253位元組之間,big節點長度大于254位元組,small節點小于254位元組。

【Redis】資料結構與對象(六種底層資料結構)——壓縮連結清單

級聯更新的時間複雜度很差,最多需要進行 N 次空間的重配置設定,每次空間的重配置設定最差需要 O(N), 是以級聯更新的時間複雜度最差是 O(N²).

與新增節點相似,删除節點也有可能會造成級聯更新的情況。

但是其實不用怕,因為級聯更新造成 Redis 性能壓力的機率極其低。

首先,級聯更新需要連續的節點大小為

250-253

之間,這本就少見,而大範圍的連續就更加少見了。如果運氣不好出現了三五個的級聯更新,也絕不會對 Redis 的性能有壓力。

繼續閱讀