1. 程式人生 > >Redis基礎(一)資料結構與資料型別

Redis基礎(一)資料結構與資料型別

Redis資料結構 === Redis一共有六種資料結構,分別是簡單動態字串、連結串列、字典、跳錶、整數集合、壓縮列表。 簡單動態字串(SDS) --- Redis只會使用C字串作為字面量,在大多數情況下,Redis使用SDS(Simple Dynamic String,簡單動態字串)作為字串表示。 SDS的資料結構: ``` struct sdshdr { // 記錄buf資料中已使用位元組的數量 // 等於SDS所儲存字串的長度 int len; // 記錄buf陣列中未使用位元組的數量 int free;; // 位元組陣列,用於儲存字串 char buf[]; } ``` ![image](https://yxl-article.oss-cn-shenzhen.aliyuncs.com/images/redis-basic/01/sds.png) 比起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; ``` ![image](https://yxl-article.oss-cn-shenzhen.aliyuncs.com/images/redis-basic/01/list.png) - 連結串列被廣泛用於實現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; ``` ![image](https://yxl-article.oss-cn-shenzhen.aliyuncs.com/images/redis-basic/01/dict.png) - 字典被廣泛用於實現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; ``` ![image](https://yxl-article.oss-cn-shenzhen.aliyuncs.com/images/redis-basic/01/skiplist.png) - 跳錶是有序集合的底層實現之一。 - Redis的跳錶實現由zskiplist和zskiplistNode兩個結構組成,其中zskiplist用於儲存跳錶資訊(比如表頭節點、表尾節點、長度),而zskiplistNode則用於表示跳錶節點。 - 每個跳錶節點的層高都是1至32之間的隨機數。 - 在同一個跳錶中,多個節點可以包含相同的分值,但每個節點的成員物件必須是唯一的。 - 跳錶中的節點按照分值大小進行排序,當分值相同時,節點按照成員物件的大小進行排序。 整數集合(intset) --- 整數集合(intset)是集合鍵的底層實現之一,當一個集合只包含整數值元素,並且這個集合的元素數量不多時,Redis就會使用整數集合作為集合鍵的底層實現。 整數集合的資料結構: ``` typedef struct intset { // 編碼方式 uint32_t encoding; // 集合包含的元素數量 uint32_t length; // 儲存元素的陣列 int8_t contents[]; } intset; ``` ![image](https://yxl-article.oss-cn-shenzhen.aliyuncs.com/images/redis-basic/01/intset.png) - 整數集合是集合鍵的底層實現之一。 - 整數集合的底層實現為陣列,這個陣列以有序、無重複的方式儲存集合元素,在有需要時,程式會根據新新增元素的型別,改變這個陣列的型別。 - 升級操作為整數集合帶來了操作上的靈活性,並且儘可能地節約了記憶體。 - 整數集合只支援升級操作,不支援降級操作。 壓縮列表(ziplist) --- 壓縮列表(ziplist)是列表鍵和雜湊鍵的底層實現之一。當一個列表鏈只包含少量列表項,並且每個列表項要麼就是小整數值,要麼就是長度比較短的字串,那麼Redis就會使用壓縮列表來做列表鍵的底層實現。 ![image](https://yxl-article.oss-cn-shenzhen.aliyuncs.com/images/redis-basic/01/ziplist.png) - 壓縮列表是一種為節約記憶體而開發的順序型資料結構。 - 壓縮列表被用作列表鍵和雜湊鍵的底層實現之一。 - 壓縮列表可以包含多個節點,每個節點可以儲存一個位元組陣列或者整數值。 - 新增新節點到壓縮列表,或者從壓縮列表中刪除節點,可能會引發連鎖更新操作,但這種操作出現的機率並不高。 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個。 參考資料 --- - [Redis設計與實現](https://book.douban.com/subject/25900156/) - [演算法實戰(一):剖析Redis常用資料型別對應的資料結構](https://time.geekbang.org/column/article