1. 程式人生 > >redis底層設計(二)——記憶體對映資料結構

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)。