redis 資料結構與物件

2021-09-19 14:34:52 字數 4379 閱讀 3840

簡單說下redis的資料結構,這些在網上也都有很詳細的解釋,《redis 設計與實現》這本書基於redis3.0的,但是現在已經5.0 了,所以有些資料結構發生了變化,其中我自己實現發現了一部分,可能還有沒發現的。慢慢實踐吧

簡單動態字串是在c語音傳統的字串基礎上構建的,其資料結構為:

struct sdshdr ;
與c語音傳統字串區別為:

c字串

sds獲取字串長度的複雜度為o(n)

獲取字串長度的複雜度為o(1)

api是不安全的,可能會造成緩衝區溢位

api是安全的,不會造成緩衝區溢位

修改字串長度n次必然需要執行n次記憶體重分配

修改字串長度n次最多需要執行n次記憶體重分配

只能儲存文字資料

可以儲存文字或者二進位制資料

可以使用所有庫中的函式

可以使用一部分庫中的函式

所以簡單動態字串,我對簡單和動態的理解是:

簡單:查詢字串長度方便

可以使用部分庫中的函式

可以儲存不止文字的資料

動態:api是安全的,不會因為增加字串長度造成緩衝區溢位

typedef struct list list;

typedef struct listnode listnode;

多個listnode可以通過prev和next指標組成雙端鍊錶。雖然僅僅使用多個listnode結構就可以組成鍊錶,但是用list來持有鍊錶的話,操作起來會更方便。

redis鍊錶實現的特性可以總結如下:

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

無環:表頭節點的prev指標和表尾節點的next指標都只想null,對鍊錶的訪問以null為終點。

帶表頭指標和表尾指標:通過list結構的head指標和tail指標,程式獲取鍊錶的表頭節點和表尾節點的複雜都都是o(1)。

帶鍊錶長度計數器:程式使用list結構的len屬性來對list持有的鍊錶節點進行技術,程式獲取鍊錶中節點數量的複雜度為o(1)。

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

字典,又稱為符號表(symbol table),關聯陣列(associative array)或者 對映(map),是一種用於儲存鍵值對的抽象資料結構。

字典也是雜湊鍵的底層實現之一:當乙個雜湊鍵包含的鍵值對比較多,又或者鍵值對中的元素都是比較長的字串時,redis就會使用字段作為雜湊鍵的底層實現。

typedef struct dictht  dictht
typeof struct dictentry v;

// 指向下個雜湊表節點,形成鍊錶

struct dictentry *next;

} dictentry

typedef struct dict  dict
雜湊演算法 & 雜湊衝突

既然是雜湊表,那麼一定要有要通過雜湊演算法來插入資料,同時還要解決雜湊衝突。

這裡不去細說雜湊演算法,而redis字典通過鏈位址法來解決鍵衝突。

漸進式rehash

同樣隨著操作的不斷進行。雜湊表的鍵值對會逐漸的增多或者減少。為了讓雜湊表的負載因子維持在乙個合理的範圍之內,當雜湊表儲存的鍵值對數量太多或者太少時,程式需要對雜湊表的大小進行相應的拓展或者收縮。拓展和收縮雜湊表的工作可以通過制定rehash(重新雜湊)操作來完成。同時rehash動作不是一次性、集中式的完成的,而是分多次、漸進式的完成的。

每個字典帶有兩個雜湊表,乙個平時使用(ht[0]),另乙個僅在進行rehash時使用(ht[1])。在rehash過程中,新新增到字典的鍵值對一律會被儲存到ht[1]裡面,而ht[0]則不再進行任何新增操作,這一措施保證了ht[0]包含的鍵值對數量會只減不增,並隨著rehash操作的執行而最終變成空表。

雜湊表漸進式rehash的詳細步驟:

為ht[1] 分配空間,讓字典同時持有ht[0]和ht[1]兩個雜湊表。

在字典維持乙個索引計數器變數 rehashidx,並將它的值設定為0,表示rehash 開始。

在rehash 進行期間,每次對字典執行新增、刪除、查詢或者更新操作時,程式除了執行指定的操作以外,還會將ht[0]雜湊表在 rehashidx 索引上的所有鍵值對 rehash到 ht[1],當rehash工作完成之後,程式將 rehashidx 屬性的值增一。

隨著字典操作的不斷執行,最終在某個時間點上 ht[0] 的所有鍵值對都會被rehash 至 ht[1],這時程式將 rehashidx 屬性的值設定為 -1,表示rehash操作已經完成。

跳躍表(skiplist)是一種有序資料結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。

跳躍表是一種隨機化的資料,跳躍表以有序的方式在層次化的鍊錶中儲存元素,效率和平衡樹媲美 ——查詢、刪除、新增等操作都可以在對數期望時間下完成,並且比起平衡樹來說,跳躍表的實現要簡單直觀得多。

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

redis的跳躍表實現由zskiplist 和 zskiplistnode 兩個結構組成,其中zskiplist 用於儲存跳躍表資訊(比如表頭界定啊,表尾節點,長度),而zskiplistnode 則用於表示跳躍表節點。

zskiplistnode 資料結構

typedef struct zskiplistnode level;

} zskiplistnode;

整數集合是集合建的底層實現之一,當乙個集合中只包含整數,且這個集合中的元素數量不多時,redis就會使用整數集合intset作為集合的底層實現。整數集合可以儲存int16,int32,int64型別的整數。

typedef struct intset
contents陣列是整數集合的底層實現,contents數字的真正型別取決於encoding屬性的值。

支援整數集合的公升級:提公升整數集合的靈活性,同時盡可能的節約記憶體。但是整數集合不支援降級操作。一旦公升級後,就會位置公升級後的狀態。

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

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

壓縮列表的構成:

zlbytes

zltail

zllen

entry-1

entry-2

…zlend

zlbytes:用於記錄整個壓縮列表占用的記憶體位元組數

zllen:記錄了壓縮列表包含的節點數量。

entry-x:要說列表包含的各個節點

zlend:用於標記壓縮列表的末端

quicklist並沒有在書中提到,因為quicklist是乙個3.2版本之後新增的基礎資料結構,是 redis 自定義的一種複雜資料結構。將ziplist和adlist結合到了乙個資料結構中。主要是作為list的基礎資料結構。

在3.2之前,list是根據元素數量的多少採用ziplist或者adlist作為基礎資料結構,3.2之後統一改用quicklist,從資料結構的角度來說quicklist結合了兩種資料結構的優缺點,複雜但是實用:

typedef struct quicklist  quicklist;
typedef struct quicklistnode  quicklistnode;
由於quicklist結構包含了壓縮表和煉表,那麼每個quicklistnode的大小就是乙個需要仔細考量的點。如果單個quicklistnode儲存的資料太多,就會影響插入效率;但是如果單個quicklistnode太小,就會變得跟鍊錶一樣造成空間浪費。

quicklist通過源**中fill對單個quicklistnode的大小進行限制,而fill欄位會讀取配置中的list-max-ziplist-size引數值。

list-max-ziplist-size -2:fill可以被賦值為正整數或負整數,當fill為負數時:

還有乙個引數list-compress-depth表示列表兩頭不壓縮的節點的個數

quicklist除了常用的增刪改查外還提供了merge、將ziplist轉換為quicklist等api,這裡就不詳解了,可以具體檢視quicklist.h和quicklist.c檔案。

Redis資料結構與物件

redis使用五種型別物件實現實現鍵值對資料庫 字串 列表 雜湊 集合 有序集合 列表編碼 ziplist或linkedlist 雜湊編碼 ziplist或hashtable 集合編碼 intset或hashtable 有序集合編碼 ziplist或skiplist struct sdshdr 二進...

Redis資料結構與物件(二)

1 物件共享 如果redis中已經set了乙個字串或數字,再set同樣值,這時會實現物件引用共享。值物件的refcount會出現變化,增加。redis set a 100 ok redis set b 100 redis object refcount a integer 2 object refc...

重現Redis 資料結構與物件(三)

整數集合 intset 是集合鍵的底層實現之一,當乙個集合只有整數值元素且元素不多時,redis使用整數集合作為集合鍵的底層實現。定義和實現分別在intset.h和intset.c中。typedef struct intset intset redis可以儲存的整數值型別為int16 t,int32...