Redis 專題一: 資料結構,redis專題資料結構
簡單的動態字串
- redis沒有直接使用C語言傳統的字串表示,而自己構建了一個動態字串SDS,當redis需要的不僅僅是一個字串字面量,而是一個可以被秀噶ide字串值時,redis就會使用sds來表示字串值,比如在redis的資料庫裡,包含字串值的索引值對在底層都是由SDS實現的。
redis > set name "bugall"ok1.索引值對的鍵是一個字串對象,對象的底層實現是一個儲存著字串"name"的SDS2.索引值對的值也是一個字串對象,對象的底層實現是一個儲存這字串"bugall"的SDS
- 除了用來儲存資料庫中的字串值之外,SDS還被用作緩衝區,AOF模組中的AOF緩衝區,以及用戶端狀態中的輸入緩衝區。
struct sdshdr{ //記錄buf數組中已使用位元組的數量 //等於SDS所儲存字串的長度 int len; //記錄buf數組中未使用位元組的數量 int free; //位元組數組,用於儲存字串 char buf[];}1. free屬性的值為0,表示這個SDS沒有分配任何未使用空間。2. len屬性的值為5,表示這個SDS儲存了一個五位元組長的字串3. buf屬性是一個char類型數組,數組的前5個位元組分別儲存了‘r','e','d','i','s'五個字元,而最後一個位元組則儲存了Null 字元'\0'注意:儲存孔子福的1位元組空間不計算在SDS的len屬性裡面,並且為空白字元分配額外的一位元組空間。
與c字串不同,SDS的空間分配策略完全杜絕了發生緩衝區溢位的可能性,當SDS api 需要對SDS進行修改時,API 會先檢查SDS的空間是否滿足修改所需要的要求,如果不滿足的話,API會自動將SDS的空間拓展至執行修改所需要的大小,然後才執行實際的修改操作,所以使用SDS即不需要手動修改SDS的空間大小,也不會出現的緩衝區溢位的情況。
空間分配策略:
為了避免C字串的這種缺陷,SDS通過未使用空間解除了字串長度和底層數組長度之間的關聯,在SDS中,buf數組的長度不一定就是字元數量加一,數組裡面可以包含未使用的位元組,而這些位元組的數量就由SDS的fress屬性記錄。通過未使用空間,SDS實現了空間的預分配和惰性空間釋放兩種最佳化策略
空間預分配:
空間預分配用於最佳化SDS的字串增長操作:當SDS的API對一個SDS進行修改,而且需要對SDS進行空間拓展的時候,程式不僅會為SDS分配修改所必須要的空間,還會為SDS分配額外的未使用空間。通過空間的預分配策略,redis可以減少連續執行字串增長操作所需的記憶體重分配次數。
惰性空間釋放
惰性空間釋放用於最佳化SDS字串縮短操作:當SDS的API需要縮短字元時,程式並不是立即使用記憶體重分配來回收縮短後多出來的位元組,而是將這些位元組的數量記錄起來(free),並等待將來使用
SDS還是二進位安全的,支援C字串函數
鏈表
鏈表提供了高效的節點重排能力,以及順序性的節點訪問方式,並且可以通過增刪節點來靈活的調整鏈表的長度。因為Redis使用的C語言並沒有內建這種資料結構,所以Redis構建了自己的鏈表實現。鏈表在Redis中的應用非常廣泛,比如列表鍵的底層實現之一就是鏈表,當一個列表鍵包含了數量比較多的元素,又或者列表中包含的元素都是比較長的字串時,Redis就會使用鏈表作為列表鍵的底層實現。
integers列表鍵的底層實現就是一個鏈表,鏈表中的每個節點都儲存了一個整數值。除了鏈表鍵之外,發布與訂閱,慢查詢,監視器等功能也用到了鏈表,Redis伺服器本身還是用鏈表來儲存多個用戶端的狀態資訊,以及使用鏈表來構建用戶端的輸出緩衝區。
typeof struct list { //表頭借點 listNode *head //表尾節點 listNode *tail //鏈表所包含的節點數量 unsigned long len //節點值複製函數 void *(*dup)(void *ptr); //節點值釋放函數 void (*free)(void *ptr); //節點值對比函數 void (*match)(void *ptr,void *key)} list
字典
- 字典,又稱為符號表,關聯陣列或映射,是一種用於儲存索引值對的抽象資料結構,Redis的資料庫就是用使用字典作為底層實現的。除了用來表示資料庫之外,字典還是雜湊鍵的底層實現之一,當一個雜湊鍵包含的索引值對比較多,又或者索引值對中的元素都是比較長的字串時。Redis就會使用字典作為雜湊鍵的底層實現。舉個例子:
a={}a{ users:{name:'bugall',name:'yifang'}}其中'users'可以理解為上穩重的雜湊鍵,索引值對就是'users'對應的內容。
- Redis的字典使用雜湊表作為底層實現,一個雜湊表裡面可以有多個雜湊表節點,而每個雜湊表節點就儲存了字典中的一個索引值對。
typedef struct dictht{ //雜湊表數組 dictEntry **table; //雜湊表大小 unsigned long size; //雜湊表大小掩碼,用於計算索引值 //總是等於size-1 unsigned long sizemaske; //該雜湊表已有節點的數量 unsigned long used;} dictht;雜湊表節點:typedef struct dictEntry{ //鍵 void *key //值 union{ void *val; uint64_tu64; int64_ts64; } v; struct dictEntry *next;} dictEntry;next屬性是指向另一個雜湊表節點的指標,這個指標可以將多個雜湊值相同的索引值對串連一次,以此來解決衝突的問題字典:typedef struct dict { //類型特定函數 dictType *type; //私人資料 void *privdata; //雜湊表 dictht ht[2]; //rehash索引 in trehashidx;} dict;
整數集合
- 整數集合是集合鍵的底層實現之一,當一個集合只包含整數數值元素,並且這個集合的元素數量不多時,Redis就會使用整數集合作為集合鍵的底層實現。舉個例子,如果我們建立一個只包含5個元素的集合鍵,並且集合中的所有元素都是整數值,那麼這個集合鍵的底層實現就會是整數集合
typedef struct intset{ //編碼方式 uint32_t encoding; //集合包含的元素數量 uint32_t length; //儲存元素的數組 int8_t contents[];} intset;contents數組是整數集合的底層實現:整數集合的每個元素都是contents數組中的任意一個item(元素),各個項在數組中按值的大小從小到大有序的排列length屬性記錄了整數集合包含的元素數量,也是contents數組的長度。雖然intset結構將contents屬性聲明為int8_t類型的數則,但實際上contents數組並不儲存任何int8_t類型的值,contents數組的真正類型取決於encoding屬性的值。這也是整數集合的特點之一,動態更改contents的大小
- 升級
每當我們要將一個新元素添加到整數集合裡面,並且新元素的類型比整數集合現有所有元素 的類型要長時,整數集合需要先進行升級,然後才能將新元素添加到整數集合裡面
1.根據新元素的類型,擴充整數集合底層數組的空間大小,並為新元素分配空間。
2.將底層數組現有的所有元素都轉換成與新元素相同的類型,並將類型轉換後的元素放置到正確的位置上,而且在放置元素的過程中,需要繼續維持底層數組的有序性質不變。
3.將新元素放到底層數組裡面
注意整數集合不支援降級操作
壓縮列表
壓縮列表是列表鍵和雜湊鍵的底層實現之一。當一個列表鍵只包含少量清單項目,並且每個清單項目要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做列表鍵的底層實現。
另外,當一個雜湊鍵只包含少量索引值對,並且每個索引值對的鍵和值要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做雜湊鍵的底層實現。
壓縮列表是Redis為了節約記憶體而開發的,是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,一個壓縮列表可以包含任意多個節點,每個節點可以儲存一個位元組數組或者一個整數值
每個壓縮列節點可以儲存一個位元組數組或是一個整數值,每個壓縮列表節點都由previous_entry_length,econding,content三個部分組成
1.previous_entry_length記錄了壓縮列表中前一個節點的長度。因為節點的previous_entry_length屬性記錄了前一個節點的長度,所以程式可以通過指標運算,根據當前節點的起始地址來計算出前一個節點的起始地址。
2.encoding屬性記錄了節點的content屬性所儲存資料的類型以及長度
3.content屬性負責儲存節點的值,節點值可以是一個位元組數組或者整數,值的類型和長度由節點的encoding屬性決定
屬性 |
類型 |
位元組長度 |
用途 |
zlbytes |
uint32_t |
4 |
記錄整個壓縮列表佔用的記憶體位元組數:在對壓縮列表進行記憶體重分配,或者計算zlend的位置時使用 |
zltail |
uint32_t |
4 |
記錄壓縮列表尾節點距離壓縮列表的起始地址有多少位元組:通過這個位移量,程式無需遍曆真箇壓縮列表就可以確定表尾節點的地址 |
zllen |
uint16_t |
2 |
記錄了壓縮列表包含的節點數量:當這個屬性的值小雨uint16_max時,這個屬性的值就是壓縮列表包含節點的數量;當這個值等於uint16_max時,節點的真實數量需要遍曆整個壓縮列表才能計算得出 |
entryX |
列表節點 |
不定 |
壓縮列表包含的各個節點,節點的長度由節點儲存的內容決定 |
zlend |
uint8_t |
1 |
特殊值0xFF(十進位255),用於標記壓縮列表的末端 |
著作權聲明:本文為博主原創文章,未經博主允許不得轉載。