redis-壓縮列表

redis-壓縮列表

壓縮列表是列表鍵和哈希鍵的底層實現之一。當列表和哈希表滿足一定條件的時候,底層將採用壓縮列表實現,具體將在之後介紹redis對象的時候詳細講解。

壓縮列表的定義

壓縮列表是Redis爲了節約內存的使用而開發的。壓縮列表使用一塊經過特殊編碼的連續內存。壓縮列表包括多個節點,每個節點可以是一個字節數組或一個整數值。
這裏寫圖片描述

圖1

圖1是壓縮列表的組成部分,各個字段解釋如下:

屬性 類型 長度 用途
zlbytes uint32_t 4字節 記錄整個壓縮列表佔有的字節數
zltail uint32_t 4字節 記錄壓縮列表表尾節點距離壓縮列表起始地址有多少個字節
zllen uint16_t 2字節 記錄了壓縮列表包含的節點數量:當這個屬性值小於65535時,這個值就是包含的節點數量,否則需要遍歷列表才能計算包含的節點數量
entryX 列表節點 不定 壓縮列表包含的節點,節點的長度由節點保存的內容決定
zlend uint8_t 1字節 特殊值0xFF(10進制255),用於標記壓縮列表的末端

壓縮列表節點的定義

壓縮列表節點可以保存一個字節數組或者一個整數。壓縮列表節點的組成部分如圖2示。
這裏寫圖片描述

圖2

  • previous_entry_length表示的是前一個節點長度,以字節爲單位。previous_entry_length屬性的長度可以是1字節或者5字節。

    • 如果前一節點的長度小於254字節,那麼previous_entry_length的長度爲1字節,前一節點的長度就保存在這1個字節裏。例如對於圖3,指示前一節點的長度爲8字節。

    這裏寫圖片描述

    圖3

    • 如果前一節點的長度大於等於254字節,那麼previous_entry_length的長度爲5字節,其中第一個字節爲0xFE,後四個字節用於保存前一個節點的長度。例如對於圖4,指示前一節點的長度爲257(0X0101)字節。
      這裏寫圖片描述

    圖4

    因爲previous_entry_length記錄了前一節點的長度,所以程序可以根據當前節點的指針計算出前一節點的指針,從而可以從尾部向頭部遍歷。

  • encoding屬性記錄了節點content屬性所保存數據的類型和長度。

    • 值的最高位分別爲00、01、10,長度分別是1字節、2字節、5字節,這種編碼方式表示content屬性記錄的是字節數組,字節數組的長度爲encoding去除最高2位後的其他位記錄(5字節的只有後4個字節用來記錄長度)。如下表示:(注:-表示空的,a、b、c、d、x代表實際有效的2進制數字)
    編碼 編碼長度 content保存的值
    00bbbbbb 1字節 長度小於等於63的字節數組
    01bbbbbb xxxxxxxx 2字節 長度小於等於2^14^-1的字節數組
    10—— aaaaaaaa bbbbbbbb cccccccc dddddddd 5字節 長度小於等於2^32^-1的字節數組
    • 值的最高位爲11開頭的是整數編碼,整數的類型由編碼除去最高二位的其他位記錄。如下表示:
    編碼 編碼長度 content保存的值
    11000000 1字節 int16_t類型的整數
    11010000 1字節 int32_t類型的整數
    11100000 1字節 int64_t類型的整數
    11110000 1字節 24位有符號整數
    11111110 1字節 8位有符號整數
    1111xxxx 1字節 這一編碼方式沒有content屬性,xxxx本身就記錄了0-15的值
  • content屬性記錄實際的節點值,有了encoding編碼方式,content屬性就很好理解了。舉兩個例子,圖5示一個長度爲11的字節數組節點。其中編碼方式爲00001011。

    這裏寫圖片描述

    圖5

    圖6示一個類型爲int16_t類型的整數。其中編碼方式爲11000000。

    這裏寫圖片描述

    圖6

連鎖更新

對於圖1,假設節點entry1到entryN的長度都介於250-253字節長度之間,那麼所有節點的previous_entry_length的長度都是1個字節。若此時在entry1之前插入一個長度大於等於254字節的節點,因爲entry1的previous_entry_length長度只有一個字節,因此程序需要對壓縮列表空間進行再分配,並將entry1的previous_entry_length長度從原來的1字節擴展爲5字節。

entry1擴展後增加了4個字節,它的長度介於254-257,而entry2的previous_entry_length無法保存該長度的值,這將再一次觸發再分配動作。更進一步,將引發雪崩般的效應。此所謂連鎖更新。因爲每次內存重分配的時間複雜度爲O(N),所以連鎖更新的時間複雜度爲O(N^2^)。

幸運的是,這種情況實際上並不常見,我們可以放心的使用。

參考:

  1. Redis設計與實現. 黃健宏著
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章