1. 程式人生 > >Redis原始碼剖析(七)--壓縮列表

Redis原始碼剖析(七)--壓縮列表

壓縮列表的構成

壓縮列表是為了節省記憶體而開發的,可以包含任意多個節點(entry),每一個節點可以儲存一個位元組陣列或者一個整數值

下圖展示了壓縮列表的各個組成部分,

  • zlbytes:4位元組,壓縮列表所用的位元組數
  • zltail:4位元組,記錄壓縮列表尾節點entryN距離壓縮列表的起始地址的位元組數。
  • zllen:2位元組,壓縮列表的節點數量
  • entryX:列表節點
  • zlend:1位元組,特殊值 0xFF(十進位制255),用於標記壓縮列表的末端

redis沒有使用結構體來儲存壓縮列表的資訊,而是通過巨集來定位每個成員的地址:

/*
 * ziplist 屬性巨集
 
*/ // 定位到 ziplist 的 bytes 屬性,該屬性記錄了整個 ziplist 所佔用的記憶體位元組數 #define ZIPLIST_BYTES(zl) (*((uint32_t*)(zl))) // 定位到 ziplist 的 offset 屬性,該屬性記錄了到達表尾節點的偏移量 #define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t)))) // 定位到 ziplist 的 length 屬性,該屬性記錄了 ziplist 包含的節點數量 #define ZIPLIST_LENGTH(zl) (*((uint16_t*)((zl)+sizeof(uint32_t)*2))) //
返回 ziplist 表頭的大小 #define ZIPLIST_HEADER_SIZE (sizeof(uint32_t)*2+sizeof(uint16_t)) // 返回指向 ziplist 第一個節點(的起始位置)的指標 #define ZIPLIST_ENTRY_HEAD(zl) ((zl)+ZIPLIST_HEADER_SIZE) // 返回指向 ziplist 最後一個節點(的起始位置)的指標 #define ZIPLIST_ENTRY_TAIL(zl) ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) // 返回指向 ziplist 末端 ZIP_END (的起始位置)的指標
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

 

壓縮列表節點結構

prev_entry_len成員

previous_entry_length 屬性的長度可以是 1 位元組或者 5 位元組

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

下圖 展示了一個壓縮列表節點,previous_entry_length 為 0x05 , 表示前一節點的長度為 5 位元組。

 

通過 previous_entry_length 屬性,我們可以實現從表尾向表頭遍歷操作:

舉個例子,有一個當前節點起始地址的指標 c , 那麼我們只要用指標 c 減去當前節點 previous_entry_length 屬性的值, 就可以得出一個指向前一個節點起始地址的指標 p 。

 

 

 

encoding成員

redis對位元組陣列和整數編碼提供了一組巨集定義:

/*
 * 字串編碼型別
 */
#define ZIP_STR_06B (0 << 6)
#define ZIP_STR_14B (1 << 6)
#define ZIP_STR_32B (2 << 6)

/*
 * 整數編碼型別
 */
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe

 

value成員

value成員負責根據encoding來儲存位元組陣列或整數

 

連鎖更新

  如果一個壓縮列表中,有多個連續、長度介於250位元組到253位元組之間的節點,因此記錄這些節點只需要1個位元組的prev_entry_len,如果要插入一個長度大於等於254的新節點到壓縮列表的頭部,然而原來的節點的prev_entry_len成員長度僅僅為1個位元組,無法儲存新節點的長度,因此會對新節點之後的所有prev_entry_len成員大小為1位元組的節點產生連鎖更新。同樣的,如果一個壓縮列表中,是多個連續的長度大於等於254的節點,當往壓縮列表的頭部插入一個長度小於254的節點,也會產生連鎖更新。另外刪除節點也會產生連鎖更新。

  下圖展示了連鎖更新的過程: