壓縮列表
壓縮列表是列表鍵和哈希鍵的底層實現之一, 當一個列表鍵只包含少量列表項, 並且每個列表項要麼就是小整數值, 要麼就是長度比較短的字符串, 那麼Redis就會使用壓縮列表來做列表鍵的底層實現. 另外, 當一個哈希鍵只包含少量鍵值對, 並且每個鍵值對的鍵和值要麼就是小整數值, 要麼就是長度比較短的字符串, 那麼Redis就會使用壓縮列表來做哈希鍵的底層實現
1.壓縮列表的構成
壓縮列表是Redis爲了節約內存而開發的, 是一系列特殊編碼的連續內存塊組成的順序型數據結構. 一個壓縮列表可以包含任意多個節點, 每個節點保存一個字節數組或者一個整數值.
屬性 | 類型 | 長度 | 用途 |
---|---|---|---|
zlbytes | unit32_t | 4字節 | 記錄整個壓縮列表佔用的內存字節數, 在對壓縮列表進行內存重分配,或者計算zlend時使用 |
zltail | unit32_t | 4字節 | 記錄壓縮列表表尾節點距離壓縮列表的的起始地址有多少字節, 通過這個偏移量, 程序無須遍歷整個壓縮列表就能確定表尾節點的地址 |
zllen | unit16_t | 2字節 | 記錄了壓縮列表包含的節點數量, 當小於UINT16_MAX時,是真正的大小,若超過這個值, 則需要遍歷整個壓縮列表才能得出 |
entryX | 列表節點 | 不定 | 壓縮列表包含的各個節點, 節點的長度由節點保存的內容決定 |
zlend | unit8_t | 1字節 | 特殊值0xFF, 用於標記壓縮列表的末端 |
2.壓縮列表的構成
每個壓縮列表可以保存一個字節數組或者一個整數值, 其中, 字節數組可以是以下三種長度之一 :
- 長度小於等於63(2^6-1)字節的字節數組
- 長度小於等於16383(2^14-1)字節的字節數組
- 長度小於等於4294967295(2^32-1)字節的字節數組
而整數值則可以使以下六種長度之一:
- 四位長,介於0至12間的無符號整數
- 1字節長的有符號整數
- 3字節長的有符號整數
- int16_t類型整數
- int32_t類型整數
- int64_t類型整數
每個壓縮列表節點都由previous_entry_length, encoding, content三個部分組成
1> previous_entry_length
節點的previous_entry_length屬性以字節爲單位, 記錄了壓縮列表中前一個節點的長度. previous_entry_length屬性的長度可以是1字節或者是5字節
- 如果前一節點的長度小於254字節, 那麼previous_entry_length長度 爲1字節, 前一節點的長度就保存在這一個字節裏面
- 如果前一節點的長度大於等於253字節, 那麼previous_entry_length屬性 的長度就爲5字節 : 其中屬性的第一字節會被設置爲0xFE(254),而之後的四個字節則用於保存前一節點的長度
通過previous_entry_length屬性, 可以根據一個節點的地址減去該節點的previous_entry_length值來得到這個節點前一個節點的起始地址. 壓縮列表從表尾向表頭遍歷操作就是用這一原理實現的
2> encoding
節點的encoding屬性記錄了節點的content屬性所保存數據的類型以及長度:
-
一字節,兩字節或者五字節長, 值的最高位爲00,01或者10的是字節數組 : 這種編碼表示節點的 content屬性保存着字節數組, 數組的長度由編碼取出最高兩位之後的其他位記錄
-
一字節, 值的最高位是以11開頭的是整數編碼 : 這種編碼表示節點的content屬性保存着整數值, 整數值的類型和長度由編碼出去最高兩位之後的其他位記錄
3> content
節點的content屬性負責保存節點的值, 節點值可以是一個字節數組或者整數, 值的類型和長度由節點的encoding屬性決定.例如;
3.連鎖更新
前面說到過, previous_entry_length屬性都記錄了前一個節點的長度, 可能是一字節或者五字節, 這時如果在某一個所有節點的大小都爲250-253字節的壓縮列表中, 在表頭位置插入了一個長度大於254字節的節點, 引起其後方第一個節點的previous_entry_length從一字節更新爲五字節, 這時此節點的大小也超過了254字節, 從而持續繼續引發後面節點的更新, Redis將在這種特殊情況下產生的多次空間擴展操作稱之爲"連鎖更新". 除了添加新節點可能會引發連鎖更新之外, 刪除節點也可能會引發連鎖更新
因爲連鎖更新在最壞情況下需要對壓縮列表歐之星N次空間重分配操作, 而每次空間重分配的最壞複雜度爲O(N), 所以連鎖更新的最壞複雜度爲O(N^2)
需要注意的是, 儘管連鎖更新的複雜度較高, 但它真正造成性能問題的機率是很低的:
- 首先,壓縮列表裏要恰好有多個連續的, 長度介於250字節至253字節之間的節點, 連鎖更新纔有可能被引發, 在實際中, 這種情況並不常見
- 其次, 即使出現連鎖更新, 但只要被更新節點的數量不多, 就不會造成任何影響
基於以上原因, zskiplist等命令的平均複雜度僅爲O(N), 在實際中, 我們可以放心的使用這些函數, 而不必擔心連鎖更新會影響壓縮列表的性能.