redis底層設計(二)——記憶體對映資料結構
我們繼續接著上一篇部落格,今天來看看記憶體對映資料結構。
上篇我們講了內部資料結構,雖然內部資料結構非常強大,但是建立一系列完整的資料結構本身也是一件相當耗費時間的工作,當一個物件包含的元素數量並不多,或者元素本身的體積並不大時,使用代價高昂的內部資料結構並不是最好的辦法。因此我們會用記憶體對映資料結構來代替內部資料結構。
記憶體對映資料結構是一系列經過特殊編碼的位元組序列,建立他們所消耗的記憶體通常比作用類似的內部資料結構要少得多,如果使用得當,記憶體對映資料結構可以為使用者節省大量的記憶體。不過,記憶體對映資料結構的編碼和操作方式要比內部資料結構複雜的多,所以記憶體對映資料結構所佔用的CPU時間會比作用類似的內部結構要多。
2.1整數集合
整數集合(intset)用於有序、無重複地儲存多個整數值,他會根據元素的值,自動選擇該用什麼長度的整數型別來儲存元素。
2.1.1 整數集合的應用
intset是集合鍵的底層實現之一,如果一個集合滿足:
* 值儲存著整數元素;
* 元素的數量不多;
那麼就會使用intset來儲存集合元素。
2.1.2 資料結構和主要操作
typedef struct intset { // 儲存元素所使用的型別的長度 uint32_t encoding; // 元素個數 uint32_t length; // 儲存元素的陣列 int8_t contents[]; } intset;
encoding 的值可以是以下三個常量的其中一個(定義位於intset.c ):
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
contents陣列是實際儲存元素的地方,陣列有一下兩個特性:
* 沒有重複元素;
* 從小到大排序;
contents的 int8_t型別只是作為一個佔位符使用,intset不使用int8_t型別儲存任何元素。新增元素預設的encoding是int16_t,當新增的新元素不適合於當前intset的編碼型別時,intset集合將會進行升級。
2.1.3 小結
* intset用於有序、無重複的儲存多個整數值。他會根據元素的值,自動選擇該用什麼長度的整數型別來儲存元素;
* 當一個位長度更長的整數值新增到intset時,需要對intset進行升級,新intset中的每個元素的位長度都等於新新增值的位長度,但原有元素的值不變;
* 升級會引起整個intset進行記憶體重分配,並移動集合中的所有元素,這個操作的複雜度為O(N);
* intset只支援升級,不支援降級;
* intset是有序的,程式使用二分法查詢演算法來實現查詢操作,複雜度為O(lgN);
2.2 壓縮列表
ziplist 是由一系列特殊編碼的記憶體塊構成的列表,一個ziplist 可以包含多個節點(entry),每個節點可以儲存一個長度受限的字元陣列(不以\0結尾的char陣列)或者整數,包括:
• 字元陣列
– 長度小於等於63 (26 - 1)位元組的字元陣列
– 長度小於等於16383 (214 - 1)位元組的字元陣列
– 長度小於等於4294967295 (232 - 1)位元組的字元陣列
• 整數
– 4 位長,介於0 至12 之間的無符號整數
– 1 位元組長,有符號整數
– 3 位元組長,有符號整數
– int16_t 型別整數
– int32_t 型別整數
– int64_t 型別整數
因為ziplist節約記憶體的性質,它被雜湊鍵、列表建和有序集合鍵作為初始化的底層實現來使用。
2.2.1 ziplist的結構:
因為ziplist header 部分的長度總是固定的(4 位元組+ 4 位元組+ 2 位元組),因此將指標移動到表頭節點的複雜度為常數時間;除此之外,因為表尾節點的地址可以通過zltail 計算得出,因此將指標移動到表尾節點的複雜度也為常數時間。
因為ziplist 由連續的記憶體塊構成,在最壞情況下,當ziplistPush 、ziplistDelete 這類對節點進行增加或刪除的函式之後,程式需要執行一種稱為連鎖更新的動作來維持ziplist 結構本身的性質,所以這些函式的最壞複雜度都為O(N2) 。不過,因為這種最壞情況出現的概率並不高,所以大可以放心使用ziplist ,而不必太擔心出現最壞情況。
2.2.2 節點的構成:
pre_entry_length:記錄了前一個節點的長度,通過這個值,可以進行指標計算,從而跳轉到上一個節點。(注:若前一個節點的長度小於254位元組,則使用一個位元組儲存pre_entry_length的值,若大於等於254,則使用5個位元組儲存,其中第一個位元組儲存254,後4個位元組儲存前一個節點的實際長度);
encoding:記錄了content的資料型別,長度為2個bit,它的值可以是00、01、10和11(其中00、01和10表示Content中儲存著字元陣列;11表示content中儲存著整數);
length:記錄了content的資料長度;
content:儲存著節點的內容
2.2.3 小結:
* ziplist是由一系列特殊編碼的記憶體塊構成的列表,它可以儲存字元陣列或整數值,它還是雜湊鍵、列表鍵和有序集合鍵的底層實現之一。
* 新增和刪除ziplist節點有可能會引起連鎖更新,因此,新增和刪除操作的最壞複雜度為O(N2),不過,因為連鎖更新的出現概率並不高,所以一般可以將新增和刪除操作的複雜度視為O(N)。