Redis資料結構
Redis一共有六種資料結構,分別是簡單動態字串、連結串列、字典、跳錶、整數集合、壓縮列表。
簡單動態字串(SDS)
Redis只會使用C字串作為字面量,在大多數情況下,Redis使用SDS(Simple Dynamic String,簡單動態字串)作為字串表示。
SDS的資料結構:
struct sdshdr {
// 記錄buf資料中已使用位元組的數量
// 等於SDS所儲存字串的長度
int len;
// 記錄buf陣列中未使用位元組的數量
int free;;
// 位元組陣列,用於儲存字串
char buf[];
}
比起C字串,SDS具有以下優點:
- 常數複雜度獲取字串長度
- 杜絕緩衝區溢位
- 減少修改字串時帶來的記憶體重分配次數
- 二進行安全
- 相容部分C字串函式
連結串列(list)
連結串列的資料結構:
typedef struct listNode {
// 前置節點
struct listNode *prev;
// 後置節點
struct listNode *next;
// 節點的值
void *value;
}listNode;
typedef struct list {
// 表頭節點
listNode *head;
// 表尾節點
listNode *tail;
// 連結串列所包含的節點數量
unsigned long len;
// 節點值複製函式
void *(*dup)(void *ptr);
// 節點值複製函式
void (*free)(void *ptr);
// 節點值對比函式
int (*match)(void *ptr,void *key);
}list;
- 連結串列被廣泛用於實現Redis的各種功能,比如列表鍵、釋出與訂閱、慢查詢、監視器等。
- 每個連結串列節點由一個listNode結構來表示,每個節點都有一個指向前置節點和後置節點的指標,所以Redis的連結串列實現是雙端連結串列。
- 每個連結串列使用一個list結構來表示,這個結構帶有表頭節點指標、表尾節點指標,以及連結串列長度等資訊。
- 因為連結串列表頭節點的前置節點和表尾節點的後置節點都指向NULL,所以Redis的連結串列實現是無環連結串列。
- 通過為連結串列設定不同的型別特定函式,Redis的連結串列可以用於儲存各種不同型別的值。
字典(dict)
字典的資料結構:
typedef struct dictht {
// 雜湊表陣列
dictEntry **table;
// 雜湊表大小
unsigned long size;
// 雜湊表大小掩碼,用於計算索引值
unsigned long sizemask;
// 該雜湊表已有節點的數量
unsigned long used;
}dictht;
typedef struct dictEntry {
// 鍵
void *key;
// 值
union {
void *val;
uint64_tu64;
int64_ts64;
}v;
// 指向下一個雜湊表節點,形成鍵表
struct dictEntry *next;
}dictEntry;
typedef struct dict {
// 型別特定函式
dictType *type;
// 私有資料
void *privdate;
// 雜湊表
dictht ht[2];
// rehash索引
// 當rehash不在進行時,值為-1
in trehashidx; /* rehashing not in progress if rehashidx == -1 */
}dict;
typedef struct dictType {
// 計算雜湊值的函式
unsigned int (*hashFunction)(const void *key);
// 複製鍵的函式
void *(*keyDup)(void *privdata, const void *key);
// 複製值的函式
void *(*valDup)(void *privdata, const void *obj);
// 對比鍵的函式
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 銷燬鍵的函式
void (*keyDestructor)(void *privdata, void *key);
// 銷燬值的函式
void (*valDestructor)(void *privdata, void *obj);
}dictType;
- 字典被廣泛用於實現Redis的各種功能,其中包括資料庫和雜湊鍵。
- Redis中的字典使用雜湊表作為底層實現,每個字典帶有兩個雜湊表,一個平時使用,另一個僅在進行rehash時使用。
- 當字典被用作資料庫的底層實現,或者雜湊鍵的底層實現時,Redis使用MurmurHash2演算法來計算鍵的雜湊值。
- 雜湊表使用鍵地址法來解決鍵衝突,被分配到同一個索引上的多個鍵值對會連線成一個單向連結串列。
- 在對雜湊表進行擴充套件或者收縮操作時,程式需要將現有雜湊表包含的所有鍵值對rehash到新雜湊表裡面,並且這個rehash過程並不是一次性地完成的,而是漸進式地完成的。
跳錶(skiplist)
跳錶(skiplist)是一種有序資料結構,它通過在每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的。跳錶查詢的時間複雜度O(logN)、最壞情況是O(N),還可以通過順序操作來指處理節點。
跳錶的資料結構:
typedef struct zskiplistNode {
// 層
struct zskiplistLevel {
// 前進指標
struct zskiplistNode * forward;
// 跨度
unsigned int span;
} level[];
// 後退指標
struct zskiplistNode *backward;
// 分值
double score;
// 成員物件
robj *obj;
} zskiplistNode;
typedef struct zskiplist {
// 表頭節點和表尾節點
struct zskiplistNode *header, *tail;
// 表中節點數量
unsigned long length;
// 表中層數最大的節點的層數
int level;
} zskiplist;
- 跳錶是有序集合的底層實現之一。
- Redis的跳錶實現由zskiplist和zskiplistNode兩個結構組成,其中zskiplist用於儲存跳錶資訊(比如表頭節點、表尾節點、長度),而zskiplistNode則用於表示跳錶節點。
- 每個跳錶節點的層高都是1至32之間的隨機數。
- 在同一個跳錶中,多個節點可以包含相同的分值,但每個節點的成員物件必須是唯一的。
- 跳錶中的節點按照分值大小進行排序,當分值相同時,節點按照成員物件的大小進行排序。
整數集合(intset)
整數集合(intset)是集合鍵的底層實現之一,當一個集合只包含整數值元素,並且這個集合的元素數量不多時,Redis就會使用整數集合作為集合鍵的底層實現。
整數集合的資料結構:
typedef struct intset {
// 編碼方式
uint32_t encoding;
// 集合包含的元素數量
uint32_t length;
// 儲存元素的陣列
int8_t contents[];
} intset;
- 整數集合是集合鍵的底層實現之一。
- 整數集合的底層實現為陣列,這個陣列以有序、無重複的方式儲存集合元素,在有需要時,程式會根據新新增元素的型別,改變這個陣列的型別。
- 升級操作為整數集合帶來了操作上的靈活性,並且儘可能地節約了記憶體。
- 整數集合只支援升級操作,不支援降級操作。
壓縮列表(ziplist)
壓縮列表(ziplist)是列表鍵和雜湊鍵的底層實現之一。當一個列表鏈只包含少量列表項,並且每個列表項要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做列表鍵的底層實現。
- 壓縮列表是一種為節約記憶體而開發的順序型資料結構。
- 壓縮列表被用作列表鍵和雜湊鍵的底層實現之一。
- 壓縮列表可以包含多個節點,每個節點可以儲存一個位元組陣列或者整數值。
- 新增新節點到壓縮列表,或者從壓縮列表中刪除節點,可能會引發連鎖更新操作,但這種操作出現的機率並不高。
Redis資料型別
Redis中,鍵的資料型別是字串,但提供了豐富的資料儲存方式,方便開發者使用,值的資料型別有很多,常用的資料型別有五種,分別是字串(string)、列表(list)、字典(hash)、集合(set)、有序集合(sortedset)。
字串(string)
“字串(string)”這種資料結構型別非常簡單,對應到資料結構裡,就是Redis裡的簡單動態字串(SDS)。
列表(list)
列表這種資料型別支援儲存一組資料。這種資料型別對應兩種實現方法,一種是壓縮列表(ziplist),另一種是雙向迴圈連結串列。
當列表中儲存的資料量比較小的時候,列表就可以採用壓縮列表的方式實現。具體需要同時滿足下面兩個條件:
- 列表中儲存的單個資料(有可能是字串型別的)小於64位元組;
- 列表中資料個數少於512。
字典(hash)
字典型別用來儲存一組資料對。每個資料對又包含鍵值兩部分。字典型別也有兩種實現方式。一種是壓縮列表,另一種是雜湊表。
同樣,只有當儲存的資料量比較小的情況下,Redis才使用壓縮列表來實現字典型別。具體需要滿足兩個條件:
- 字典中儲存的鍵和值的大小都要小於64位元組;
- 字典中鍵值對的個數要小於512個。
集合(set)
集合這種資料型別用來儲存一組不重複的資料。有兩種實現方式:一種是基於有序陣列,另一種是基於雜湊表。
當要儲存的資料,同時滿足下面這樣兩個條件的時候,Redis就採用有序陣列,來實現集合這種資料型別。
- 儲存的資料都是整數;
- 儲存的資料元素個數不超過512個。
有序集合(sortedset)
有序集合用來儲存一組資料,並且每個資料會附帶一個得分。通過得分的大小,我們將資料組織成跳錶這種資料結構,以支援快速地按照得分值、得分割槽間獲得資料。
有序集合也有兩種實現方式:跳錶和壓縮列表。使用壓縮列表來實現有序集合的前提:
- 所有資料的大小都要小於64位元組;
- 元素個數要小於128個。