上篇我們講了內部資料結構,雖然內部資料結構非常強大,但是建立一系列完整的資料結構本身也是一件相當耗費時間的工作,當乙個物件包含的元素數量並不多,或者元素本身的體積並不大時,使用代價高昂的內部資料結構並不是最好的辦法。因此我們會用記憶體對映資料結構來代替內部資料結構。
記憶體對映資料結構是一系列經過特殊編碼的位元組序列,建立他們所消耗的記憶體通常比作用類似的內部資料結構要少得多,如果使用得當,記憶體對映資料結構可以為使用者節省大量的記憶體。不過,記憶體對映資料結構的編碼和操作方式要比內部資料結構複雜的多,所以記憶體對映資料結構所占用的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外。另外的能夠提高資料庫...