1 簡單動態字符串
struct sdshdr { unsigned int len; unsigned int free; char buf[]; };
redis> set name intrack
Redis將在數據庫中創建一個新的鍵值對,其中鍵是一個字符串,一個保存着"name"的sds;值是一個字符串,一個保存着"intrack"的sds。
- 常數復雜度獲取字符長度
- 避免緩沖區溢出
- 減少修改字符操作時引起的內存分配次數(注意:free內存大小最大為1M)
- 二進制安全的
- 兼容部分C字符串函數(因為字符串后面以'\0'結尾)
2 鏈表
typedef struct listNode { struct listNode *prev; struct listNode *next; void *value; } listNode;
typedef struct list { listNode *head; listNode *tail; void *(*dup)(void *ptr); void (*free)(void *ptr); int (*match)(void *ptr, void *key); unsigned long len; } list;
list結構鏈表提供了表頭指針head、表尾指針tail及鏈表長度len,而dup/free/match用於實現存儲類型無關鏈表所需的類型特性函數。dup用於復制一個鏈表節點、free用於釋放一個鏈表節點、match用於匹配鏈表節點和輸入的值是否相等。

- 鏈表被廣泛用於實現Redis的各種功能,比如列表鍵、發布與訂閱、慢查詢、監視器等。
- 每個鏈表節點由一個listNode結構表示,每個節點都有一個指向前置節點和后置節點的指針,所以Redis中鏈表是雙向鏈表。
- 每個鏈表使用一個list結構表示,這個結構有表頭節點指針、表尾節點指針、以及鏈表長度信息。
- 鏈表表頭節點的前置節點和表尾的后置節點都指向NULL,所以Redis鏈表是無環鏈表。
- 通過將鏈表設置不同類型的特定函數,使得Redis鏈表可存儲不同類型的值。
3 字典
redis> set msg "hello world"
在數據庫中創建了一個鍵為msg,值為hello world的鍵值對時,這個鍵值對就保存在代表數據庫的字典里面的。除了用作數據庫之外,字典還是哈希鍵的底層實現之一。
typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; // 哈希表大小掩碼,用於計算索引值 unsigned long used; // 已有節點數量 } dictht;
typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; } dictEntry;
key保存鍵值對中的鍵,v屬性保存值信息,值可以是一個指針/uint64_t整數/int64_t整數。next指向下一個哈希表節點指針,解決鍵值對沖突問題。
Redis的字典由dict結構定義: typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ int iterators; /* number of iterators currently running */ } dict;
type屬性和privdata屬性是針對不同類型的鍵值對,為創建可以存儲多種類型的字典而設置的。
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中字典使用哈希表作為底層實現,每個字典有2個哈希表,一個平時使用,另一個只在rehash時使用。
- 當字典作為數據庫的底層實現,或者作為哈希鍵的底層實現時,使用MurmurHash2算法計算鍵的哈希值。
- 哈希表使用分離連接法解決鍵沖突問題,被分配到同一個索引上多個鍵值會連接成一個單向鏈表。
- 在對哈希表進行擴展或者縮容操作時,需要將現有哈希表中鍵值對rehash到新哈希表中,這個rehash過程不是一次性完成的,而是漸進的。
4 跳躍表

typedef struct zskiplistNode { robj *obj; // Redis對象 double score; struct zskiplistNode *backward; struct zskiplistLevel { struct zskiplistNode *forward; unsigned int span; } level[]; } zskiplistNode;
span屬性用於記錄兩個節點之間的距離,指向NULL的forward值都為0。節點的分值score是一個浮點數,跳躍表中所有節點都按照分值從小到大排列。obj屬性必須指向一個字符串對象,而字符串則保存着一個sds。
typedef struct zskiplist { struct zskiplistNode *header, *tail; unsigned long length; int level; } zskiplist;
header和tail指針分表指向跳躍表的表頭和表尾節點,通過length屬性記錄表長度,level屬性用於保存跳躍表中層高最大的節點的層高值。每個跳躍表節點層高都是1~32的隨機值,在同一個跳躍表中,多個節點可以包含相同的分值,但是每個節點的成員對象必須是唯一的。當分值相同時,節點按照成員對象的大小排序。

5 整數結合
typedef struct intset { uint32_t encoding; // 16/32/64編碼 uint32_t length; // 數組長度 int8_t contents[]; } intset;
contents數組用於存儲整數,數組中的值按照值的大小從小到大有序排列,並且不會包含重復項。當encoding編碼的是int型整數的話,那么contents數組中每4項用於保存一個int型整數。

6 壓縮列表

7 Redis中的對象
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr; } robj;
type表示對象類型,對於Redis鍵值對來說,鍵永遠都是字符串,值可以是字符串、列表、哈希表、集合、有序集合中的一種。encoding表示對象編碼,也就是該對象使用什么底層數據結構實現。ptr指向對象的底層數據結構。

7.1 字符串對象

embstr編碼方式是redisObject結構和sdshdr結構在一塊內存中,使用embstr對象只需要調用一次內存分配函數即可,而raw方式需要調用2次。因為在同一塊內存中,所以對緩存是友好的。




注意,linkedlist編碼的列表對象在底層雙端列表中包含了多個字符串對象,這個嵌套字符串對象行為在哈希表、集合中都會出現,字符串對象是Redis五種類型中唯一一種會被其他四種類型對象嵌套的對象。






以上命令對應的壓縮列表視圖如下所示:

typedef struct zset { dict *dict; zskiplist *zsl; } zset;
zset中的zsl跳躍表按分值從小到大保存了所有集合元素,每個跳躍表節點保存一個集合元素,跳躍表節點的object屬性保存元素的成員,score屬性保存元素的分值。通過該跳躍表,可以對有序集合進行范圍型操作,比如zrank、zrange命令就是基於跳躍表實現的。
