圖解Redis之資料結構篇——壓縮列表

崖邊小生發表於2019-08-06

前言

    同整數集合一樣壓縮列表也不是基礎資料結構,而是 Redis 自己設計的一種資料儲存結構。它有點兒類似陣列,通過一片連續的記憶體空間,來儲存資料。不過,它跟陣列不同的一點是,它允許儲存的資料大小不同。

一、壓縮列表

    聽到“壓縮”兩個字,直觀的反應就是節省記憶體。之所以說這種儲存結構節省記憶體,是相較於陣列的儲存思路而言的。我們知道,陣列要求每個元素的大小相同,如果我們要儲存不同長度的字串,那我們就需要用最大長度的字串大小作為元素的大小(假設是20個位元組)。儲存小於 20 個位元組長度的字串的時候,便會浪費部分儲存空間。

圖解Redis之資料結構篇——壓縮列表

    陣列的優勢佔用一片連續的空間可以很好的利用CPU快取訪問資料。如果我們想要保留這種優勢,又想節省儲存空間我們可以對陣列進行壓縮。

圖解Redis之資料結構篇——壓縮列表

     但是這樣有一個問題,我們在遍歷它的時候由於不知道每個元素的大小是多少,因此也就無法計算出下一個節點的具體位置。這個時候我們可以給每個節點增加一個lenght的屬性。

圖解Redis之資料結構篇——壓縮列表

    如此。我們在遍歷節點的之後就知道每個節點的長度(佔用記憶體的大小),就可以很容易計算出下一個節點再記憶體中的位置。這種結構就像一個簡單的壓縮列表了。

二、Redis壓縮列表

    壓縮列表(zip1ist)是列表和雜湊的底層實現之一。

    當一個列表只包含少量列表項,並且每個列表項要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做列表的底層實現。

    當一個雜湊只包含少量鍵值對,比且每個鍵值對的鍵和值要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做雜湊的底層實現。

2.1 Redis壓縮列表的構成

    壓縮列表是Redis為了節約記憶體而開發的,是由一系列特殊編碼的連續記憶體塊組成的順序型(sequential)資料結枃。一個壓縮列表可以包含任意多個節點(entry),每個節點可以儲存一個位元組陣列或者一個整數值,如下圖。

圖解Redis之資料結構篇——壓縮列表

示例:

圖解Redis之資料結構篇——壓縮列表

    如上圖,展示了一個總長為80位元組,包含3個節點的壓縮列表。如果我們有一個指向壓縮列表起始地址的指標p,那麼表為節點的地址就是P+60。

2.2 Redis壓縮列表節點的構成

    每個壓縮列表節點可以儲存一個位元組陣列或者一個整數值。其中,位元組陣列可以是以下三種長度中的一種。

  • 長度小於等於63(2^6-1)位元組的位元組陣列;
  • 長度小於等於16383(2^14-1)位元組的位元組陣列
  • 長度小於等於4294967295(2^32-1)位元組的位元組陣列

整數值可以是以下6種長度中的一種

  • 4位長,介於0至12之間的無符號整數
  • 1位元組長的有符號整數
  • 3位元組長的有符號整數
  • int16_t型別整數
  • int32_t型別整數
  • int64_t型別整數

圖解Redis之資料結構篇——壓縮列表

    節點的 previous_entry_length屬性以位元組為單位,記錄了壓縮列表中前一個節點的長度。 previous_entry_length屬性的長度可以是1位元組或者5位元組。

  • 如果前一節點的長度小於254位元組,那麼 previous_entry_length屬性的長度為1位元組,前一節點的長度就儲存在這一個位元組裡面。
  • 如果前一節點的長度大於等於254位元組,那麼 previous_entry_length屬性的長度為5位元組:其中屬性的第一位元組會被設定為0xFE(十進位制值254),而之後的四個位元組則用於儲存前一節點的長度.

    節點的encoding屬性記錄了節點的content屬性所儲存資料的型別以及長度。

  • 一位元組、兩位元組或者五位元組長,值的最高位為00、01或者10的是位元組陣列編碼這種編碼表示節點的 content屬性儲存著位元組陣列,陣列的長度由編碼除去最高兩位之後的其他位記錄。
  • 一位元組長,值的最高位以11開頭的是整數編碼:這種編碼表示節點的content屬性儲存著整數值,整數值的型別和長度由編碼除去最高兩位之後的其他位記錄。

    節點的content屬性負責儲存節點的值,節點值可以是一個位元組陣列或者整數,值的型別和長度由節點的encoding屬性決定。

圖解Redis之資料結構篇——壓縮列表

  • 編碼的最高兩位00表示節點儲存的是一個位元組陣列。
  • 編碼的後六位001011記錄了位元組陣列的長度11。
  • content屬性儲存著節點的值"hello world"。
  • 編碼11000000表示節點儲存的是一個int16_t型別的整數值;
  • content屬性儲存著節點的值10086

2.3 常用操作的時間複雜度

操作 時間複雜度
建立一個新的壓縮列表 O(1)
建立一個包含給定值的新節點,並將這個新節點新增到壓縮列表的表頭或者表尾 平均O(N),最壞O(N^2)(可能發生連鎖更新)
將包含給定值的新節點插人到給定節點之後 平均O(N),最壞O(N^2)(可能發生連鎖更新)
返回壓縮列表給定索引上的節點 O(N)
在壓縮列表中査找並返回包含了給定值的節點 因為節點的值可能是一個位元組陣列,所以檢查節點值和給定值是否相同的複雜度為O(N),而查詢整個列表的複雜度則為(N^2)
返回給定節點的下一個節點 O(1)
返回給定節點的前一個節點 O(1)
獲取給定節點所儲存的值 O(1)
從壓縮列表中刪除給定的節點 平均O(N),最壞O(N^2)(可能發生連鎖更新)
刪除壓縮列表在給定索引上的連續多個 平均O(N),最壞O(N^2)(可能發生連鎖更新)
返回壓縮列表目前佔用的記憶體位元組數 O(1)
返回壓縮列表目前包含的節點數量 點數量小於65535時為O(1),大於65535時為O(N)

本文重點

  • 壓縮列表是Redis為節約記憶體自己設計的一種順序型資料結構。

  • 壓縮列表被用作列表鍵和雜湊鍵的底層實現之一。
  • 壓縮列表可以包含多個節點,每個節點可以儲存一個位元組陣列或者整數值。
  • 新增新節點到壓縮列表,或者從壓縮列表中刪除節點,可能會引發連鎖更新操作,但這種操作出現的機率並不高。

參考

《Redis設計與實現》

《Redis開發與運維》

《Redis官方文件》

-----END-----

相關文章