redis底層設計(二) 記憶體對映資料結構

2022-03-02 16:04:52 字數 2741 閱讀 6787

上篇我們講了內部資料結構,雖然內部資料結構非常強大,但是建立一系列完整的資料結構本身也是一件相當耗費時間的工作,當乙個物件包含的元素數量並不多,或者元素本身的體積並不大時,使用代價高昂的內部資料結構並不是最好的辦法。因此我們會用記憶體對映資料結構來代替內部資料結構。

記憶體對映資料結構是一系列經過特殊編碼的位元組序列,建立他們所消耗的記憶體通常比作用類似的內部資料結構要少得多,如果使用得當,記憶體對映資料結構可以為使用者節省大量的記憶體。不過,記憶體對映資料結構的編碼和操作方式要比內部資料結構複雜的多,所以記憶體對映資料結構所占用的cpu時間會比作用類似的內部結構要多。

2.1整數集合

整數集合(intset)用於有序、無重複地儲存多個整數值,他會根據元素的值,自動選擇該用什麼長度的整數型別來儲存元素。

2.1.1 整數集合的應用

intset是集合鍵的底層實現之一,如果乙個集合滿足:

* 值儲存著整數元素;

* 元素的數量不多;

那麼就會使用intset來儲存集合元素。

2.1.2 資料結構和主要操作

typedef struct intset  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)。

Redis的字典結構底層設計

相比前兩種sds和list結構而言,字典的結構相對來說要複雜,主要涉及的都是和雜湊相關的問題,包括但不限於雜湊衝突的解決。雜湊的擴容策略,雜湊演算法等。其主要用在redis的底層資料庫底層實現,雜湊鍵的底層實現。ht屬性是乙個包含兩個項的陣列,陣列中的每個項都是乙個dictht雜湊表,一般情況下,字...

Redis字串的底層設計

redis的底層是用c語言來實現的,在c語言中字串的預設是以 0 標識結束,而redis並沒有採用這種傳統的方法表示,而是自己構建了一種簡單動態字串 sds 來表示。下面看一下什麼是sds,sds的 定義,以及在儲存乙個字串 redis 時sds和傳統c的表示分別是怎麼樣,使用sds的好處是什麼 s...

Redis考慮虛擬記憶體設計

虛擬記憶體指當物理記憶體不夠用時,借用硬碟的空間來當作物理記憶體使用,就是暫時把不經常訪問的資料從記憶體交換到磁碟中,從而騰出寶貴的 記憶體空間用於其他需要訪問的資料。尤其是對於redis這樣的記憶體資料庫,記憶體總是不夠用的。除了可以將資料分割到多個redis server外。另外的能夠提高資料庫...