Redis底層資料結構 md

2021-09-12 13:54:31 字數 4016 閱讀 3488

redis 資料庫裡面的每個鍵值對(key-value) 都是由物件(object)組成的:

資料庫鍵總是乙個字串物件(string object);

資料庫的值則可以是字串物件、列表物件(list)、雜湊物件(hash)、集合物件(set)、有序集合(sort set)物件這五種物件中的其中一種。

有以下資料型別:

簡單動態字串(sds), 鍊錶, 字典, 跳躍表, 整數集合, 壓縮列表

redis構建了一種名為簡單動態字串的抽象型別,作為預設字串表示

結構:

/*  

* 儲存字串物件的結構

*/

struct sdshdr ;

1.獲取字串長度(sds o(1)/c 字串 o(n))

傳統的c 字串 使用長度為n+1 的字串陣列來表示長度為n 的字串,所以為了獲取乙個長度為c字串的長度,必須遍歷整個字串。

和c 字串不同,sds 的資料結構中,有專門用於儲存字串長度的變數,我們可以通過獲取len 屬性的值,直接知道字串長度

2.杜絕緩衝區溢位

c 字串 不記錄字串長度,除了獲取的時候複雜度高以外,還容易導致緩衝區溢位。

假設程式中有兩個在記憶體中緊鄰著的 字串 s1 和 s2,其中s1 儲存了字串「redis」,二s2 則儲存了字串「mongodb」。將s1 的內容修改為redis cluster,但是又忘了重新為s1 分配足夠的空間。那麼s2原來的內容會被覆蓋掉。

redis 中sds 的空間分配策略完全杜絕了發生緩衝區溢位的可能性:

當我們需要對乙個sds 進行修改的時候,redis 會在執行拼接操作之前,預先檢查給定sds 空間是否足夠,如果不夠,會先拓展sds 的空間,然後再執行拼接操作。

3.減少修改字串時帶來的記憶體重分配次數   

c語言字串在進行字串的擴充和收縮的時候,都會面臨著記憶體空間的重新分配問題。

sds在拓展時會進行預分配策略, 通過這種預分配策略,sds將連續增長n次字串所需的記憶體重分配次數從必定n次降低為最多n次。

4.惰性釋放空間

sds有free屬性可以記錄剩餘空間的,當對字串進行收縮的時候,redis只記錄free的值,避免下次修改時,對字串空間進行拓展。

sds提供了相應的api,在需要的時候,自行釋放sds的空餘空間。

鍊錶提供了高效的節點重排能力,以及順序性的節點訪問方式,並且可以通過增刪節點來靈活地調整鍊錶的長度。

鍊錶在redis 中的應用非常廣泛,比如列表鍵的底層實現之一就是鍊錶。當乙個列表鍵包含了數量較多的元素,又或者列表中包含的元素都是比較長的字串時,redis 就會使用鍊錶作為列表鍵的底層實現。

1.鍊錶的資料結構listnode(雙向鍊錶):

typedef struct listnode
一般用list操作鍊錶:

typedef struct list
3.鍊錶的特性

雙端:鍊錶節點帶有prev 和next 指標,獲取某個節點的前置節點和後置節點的時間複雜度都是o(n)

無環:表頭節點的 prev 指標和表尾節點的next 都指向null,對立案表的訪問時以null為截止

表頭和表尾:因為鍊錶帶有head指標和tail 指標,程式獲取煉表頭結點和尾節點的時間複雜度為o(1)

長度計數器:鍊錶中存有記錄鍊錶長度的屬性 len

多型:鍊錶節點使用 void* 指標來儲存節點值,並且可以通過list 結構的dup 、 free、 match三個屬性為節點值設定型別特定函式。

是一種用於儲存鍵值對的抽象資料結構。 

在字典中,乙個鍵(key)可以和乙個值(value)進行關聯,字典中的每個鍵都是獨一無二的。在c語言中,並沒有這種資料結構,但是redis 中構建了自己的字典實現。

# 1.雜湊表

typedef struct dictht

# 2.雜湊表節點

typeof struct dictentry

struct dictentry *next;

}# 3.字典

typedef struct dict

1.雜湊表 我們可以看到,在結構中存有指向dictentry 陣列的指標,而我們用來儲存資料的空間既是dictentry

2.雜湊表節點 在資料結構中,我們清楚key 是唯一的,但是我們存入裡面的key 並不是直接的字串,而是乙個hash 值,通過hash 演算法,將字串轉換成對應的hash 值,然後在dictentry 中找到對應的位置。

這時候我們會發現乙個問題,如果出現hash 值相同的情況怎麼辦?redis 採用了鏈位址法(模擬於hashmap中的桶):

3.字典 type 屬性 和privdata 屬性是針對不同型別的鍵值對,為建立多型字典而設定的。

ht 屬性是乙個包含兩個項(兩個雜湊表)的陣列

隨著對雜湊表的不斷操作,雜湊表儲存的鍵值對會逐漸的發生改變,為了讓雜湊表的負載因子維持在乙個合理的範圍之內,我們需要對雜湊表的大小進行相應的擴充套件或者壓縮,這時候,我們可以通過 rehash(重新雜湊)操作來完成

跳躍表(skiplist)是一種有序資料結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。跳躍表是一種隨機化的資料,跳躍表以有序的方式在層次化的鍊錶中儲存元素,效率和平衡樹媲美 ——查詢、刪除、新增等操作都可以在對數期望時間下完成,並且比起平衡樹來說,跳躍表的實現要簡單直觀得多。

redis 只在兩個地方用到了跳躍表,乙個是實現有序集合鍵,另外乙個是在集群節點中用作內部資料結構。

redis 的跳躍表 主要由兩部分組成:zskiplist(鍊錶)和zskiplistnode (節點)

typedef struct zskiplistnode level;

//後退指標

struct zskiplistnode *backward;

//分值

double score;

//成員物件

robj *obj;

}typedef struct zskiplist zskiplist;

1、層:level 陣列可以包含多個元素,每個元素都包含乙個指向其他節點的指標。

2、前進指標:用於指向表尾方向的前進指標

3、跨度:用於記錄兩個節點之間的距離

4、後退指標:用於從表尾向表頭方向訪問節點

5、分值和成員:跳躍表中的所有節點都按分值從小到大排序。成員物件指向乙個字串,這個字串物件儲存著乙個sds值

從結構圖中我們可以清晰的看到,header,tail分別指向跳躍表的頭結點和尾節點。level 用於記錄最大的層數,length 用於記錄我們的節點數量。

總結:- 跳躍表是有序集合的底層實現之一

其實就是乙個特殊的集合,裡面儲存的資料只能夠是整數,並且資料量不能過大。

typedef struct intset
在上述資料結構圖中我們可以看到,intset 在預設情況下會幫我們設定整數集合中的編碼方式,但是當我們存入的整數不符合整數集合中的編碼格式時,就需要使用到redis 中的公升級策略來解決

intset 中公升級整數集合並新增新元素共分為三步進行:

1、根據新元素的型別,擴充套件整數集合底層陣列的空間大小,並為新元素分配空間

2、將底層陣列現有的所有元素都轉換成新的編碼格式,重新分配空間

3、將新元素加入到底層陣列中

壓縮列表是列表鍵和雜湊鍵的底層實現之一。當乙個列表鍵只把汗少量列表項,並且每個列表項要麼就是小整數,要麼就是長度比較短的字串,那麼redis 就會使用壓縮列表來做列表鍵的底層實現。

參考:

Redis底層資料結構?

福哥口訣法 簡鏈字跳整 壓快壓 sds synamic string 簡單動態字串。支援自動動態擴容的位元組陣列 list 鍊錶 雙端鍊錶。dict 字典。使用雙雜湊表實現的,支援平滑擴容的字典 zskiplist 跳躍表。附加了後向指標的跳躍表 intset 整數集合。用於儲存整數數值集合的自有結...

Redis底層資料結構

redis底層實現的8種資料結構 sds synamic string 支援自動動態擴容的位元組陣列 list 鍊錶 dict 使用雙雜湊表實現的,支援平滑擴容的字典 zskiplist 附加了後向指標的跳躍表 intset 用於儲存整數數值集合的自有結構 ziplist 一種實現上類似於tlv,但...

redis底層資料結構

1.1 string字串 表現形式為 資料結構 sds 簡單的動態字串 使用原因 redis是使用c語言開發的,但在c語言中是沒有字串型別的,只能使用指標或符陣列的形式表示乙個字串,所以在redis設計了一種簡單的動態字串 可以根據不同的資料型別不同的資料結構選擇不同的資料結構 支援的資料型別 字串...