Redis對象類型 Redis基於基礎的數據結構創建的對象: 字元串對象、 列表對象、 哈希對象、 集合對象 有序集合對象。 對象回收:Redis對象系統實現了基於引用計數技術的記憶體回收機制,當程式不再使用某個對象的時候,這個對象所占用的記憶體就會被自動釋放;Redis通過引用計數技術實現了對象共用機 ...
Redis對象類型
Redis基於基礎的數據結構創建的對象:
- 字元串對象、
- 列表對象、
- 哈希對象、
- 集合對象
- 有序集合對象。
對象回收:Redis對象系統實現了基於引用計數技術的記憶體回收機制,當程式不再使用某個對象的時候,這個對象所占用的記憶體就會被自動釋放;Redis通過引用計數技術實現了對象共用機制,在適當的條件下通過讓多個資料庫鍵共用同一個記憶體對象來節約記憶體;
一、RedisObject
在server.h文件中,給出了RedisObject的結構體定義:
typedef struct redisObject { unsigned type:4;//類型 unsigned encoding:4;//編碼 unsigned lru:LRU_BITS; //訪問時間lru int refcount;// 引用計數refcount void *ptr;//指向底層實現數據結構的指針 } robj;
- 類型type:
Redis的對象有五種類型,分別是string、hash、list、set和zset,type屬性就是用來標識著五種數據類型。type占用4個bit位,其取值和類型對應如下:
#define OBJ_STRING 0 #define OBJ_LIST 1 #define OBJ_SET 2 #define OBJ_ZSET 3 #define OBJ_HASH 4
- 編碼類型encoding:
Redis對象的編碼方式由encoding參數指定,也就是表示ptr指向的數據以何種數據結構作為底層實現。該欄位也占用4個bit位。其取值和對應類型對應如下:
#define OBJ_ENCODING_RAW 0 /* Raw representation */ #define OBJ_ENCODING_INT 1 /* Encoded as integer */ #define OBJ_ENCODING_HT 2 /* Encoded as hash table */ #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ #define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */ #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */ #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */ #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */ #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */ #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
上述這幾種編碼類型與其底層實現如下表:
編碼類型 | 底層實現 |
OBJ_ENCODING_RAW | 簡單動態字元串sds |
OBJ_ENCODING_INT | long類型的整數 |
OBJ_ENCODING_HT | 字典dict |
OBJ_ENCODING_LINKEDLIST | 雙端隊列sdlist |
OBJ_ENCODING_ZIPLIST | 壓縮列表ziplist |
OBJ_ENCODING_INTSET | 整數集合intset |
OBJ_ENCODING_SKIPLIST | 跳躍表skiplist和字典dict |
OBJ_ENCODING_EMBSTR | EMBSTR編碼的簡單動態字元串sds |
OBJ_ENCODING_QUICKLIST | 由雙端鏈表和壓縮列表構成的快速列表 |
Redis的每一種對象類型可以對應不同的編碼方式,這就極大地提升了Redis的靈活性和效率。Redis可以根據不同的使用場景,來選擇合適的編碼方式,五種對象類型對應的底層編碼方式如下表所示:
對象類型 | 編碼方式 |
OBJ_STRING 字元串類型 |
OBJ_ENCODING_RAW ,OBJ_ENCODING_INT ,OBJ_ENCODING_EMBSTR 簡單動態字元串sds;long類型的整數;EMBSTR編碼的簡單動態字元串sds |
OBJ_LIST 列表類型 |
OBJ_ENCODING_LINKEDLIST ,OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_QUICKLIST 雙端隊列sdlist;壓縮列表ziplist;由雙端鏈表和壓縮列表構成的快速列表 |
OBJ_SET 集合類型 |
OBJ_ENCODING_INTSET ,OBJ_ENCODING_HT 整數集合intset、字典dict |
OBJ_ZSET 有序集合類型 |
OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_SKIPLIST 壓縮列表ziplist、跳躍表skiplist和字典dict |
OBJ_HASH 哈希類型 |
OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_HT 壓縮列表ziplist、字典dict |
- 訪問時間lru
lru表示該對象最後一次被訪問的時間,其占用24個bit位。保存該值的目的是為了計算該對象的空轉時長,便於後續根據空轉時長來決定是否釋放該鍵,回收記憶體。鍵的空轉時長還有另外一項作用:如果伺服器打開了maxmemory選項,並且伺服器用於回收記憶體的演算法為volatile-lru或者allkeys-lru,那麼當伺服器占用的記憶體數超過了maxmemory選項所設置的上限值時,空轉時長較高的那部分鍵會優先被伺服器釋放,從而回收記憶體。
- 引用計數refcount
C語言不具備自動記憶體回收機制,所以Redis對每一個對象設定了引用計數refcount欄位,程式通過該欄位的信息,在適當的時候自動釋放記憶體進行記憶體回收。此功能與C++的智能指針相似。
- 當創建一個對象時,其引用計數初始化為1;
- 當這個對象被一個新程式使用時,其引用計數加1;
- 當這個對象不再被一個程式使用時,其引用計數減1;
- 當引用計數為0時,釋放該對象,回收記憶體。
二、t_String 字元串類型
字元串是Redis中最為常見的數據存儲類型,其底層實現是簡單動態字元串sds,因此,該字元串類型是二進位安全的,這就意味著它可以接受任何格式的數據。另外,Redis規定,字元串類型最多可以容納的數據長度為512M。Redis提供了下列函數,來檢測字元串鍵的大小。
static int checkStringLength(client *c, long long size) { // 超出了512M,就直接報錯 if (size > 512*1024*1024) { addReplyError(c,"string exceeds maximum allowed size (512MB)"); return C_ERR; } return C_OK; }
字元串對象的編碼可以是int、raw或者embstr。
- 如果一個字元串對象保存的是整數值,並且這個整數值可以用long類型來表示,那麼字元串對象會將整數值保存在字元串對象結構的ptr屬性裡面(將void*轉換成long),並將字元串對象的編碼設置為int。
- 如果字元串對象保存的是一個字元串值,並且這個字元串值的長度大於32位元組,那麼字元串對象將使用一個簡單動態字元串(SDS)來保存這個字元串值,並將對象的編碼設置為raw。
- 如果字元串對象保存的是一個字元串值,並且這個字元串值的長度小於等於32位元組,那麼字元串對象將使用embstr編碼的方式來保存這個字元串值。embstr編碼是專門用於保存短字元串的一種優化編碼方式,embstr編碼則通過調用一次記憶體分配函數來分配一塊連續的空間,空間中依次包含redisObject和sdshdr兩個結構。
三、t_list 列表對象類型
當列表對象可以同時滿足以下兩個條件時,列表對象使用ziplist編碼:
- 列表對象保存的所有字元串元素的長度都小於64位元組;
- 列表對象保存的元素數量小於512個;
不能滿足這兩個條件的列表對象需要使用linkedlist編碼。
以上兩個條件的上限值是可以修改的,配置文件中關於list-max-ziplist-value選項和list-max-ziplist-entries
- ziplist編碼的列表對象使用壓縮列表作為底層實現,每個壓縮列表節點(entry)保存了一個列表元素。
- 另一方面,linkedlist編碼的列表對象使用雙端鏈表作為底層實現,每個雙端鏈表節點(node)都保存了一個字元串對象,而每個字元串對象都保存了一個列表元素。
四、t_hash 哈希對象類型
當哈希對象可以同時滿足以下兩個條件時,哈希對象使用ziplist編碼:
- 哈希對象保存的所有鍵值對的鍵和值的字元串長度都小於64位元組;
- 哈希對象保存的鍵值對數量小於512個;
不能滿足這兩個條件的哈希對象需要使用hashtable編碼。
這兩個條件的上限值是可以修改的,配置文件中關於hash-max-ziplist-value選項和hash-max-ziplist-entrie
- ziplist編碼的哈希對象使用壓縮列表作為底層實現
每當有新的鍵值對要加入到哈希對象時,程式會先將保存了鍵的壓縮列表節點推入到壓縮列表表尾,然後再將保存了值的壓縮列表節點推入到壓縮列表表尾,因此:
- 保存了同一鍵值對的兩個節點總是緊挨在一起,保存鍵的節點在前,保存值的節點在後;
- 先添加到哈希對象中的鍵值對會被放在壓縮列表的表頭方向,而後來添加到哈希對象中的鍵值對會被放在壓縮列表的表尾方向。
- hashtable編碼的哈希對象使用字典作為底層實現
- 字典的每個鍵都是一個字元串對象,對象中保存了鍵值對的鍵;
- 字典的每個值都是一個字元串對象,對象中保存了鍵值對的值。
五、t_set 集合對象類型
集合對象的編碼可以是intset或者hashtable。
當集合對象可以同時滿足以下兩個條件時,對象使用intset編碼:
- 集合對象保存的所有元素都是整數值;
- 集合對象保存的元素數量不超過512個。
不能滿足這兩個條件的集合對象需要使用hashtable編碼。第二個條件的上限值是可以修改的,配置文件中關於set-max-intset-entries選項的說明
- intset編碼的集合對象使用整數集合作為底層實現,集合對象包含的所有元素都被保存在整數集合裡面。
- 另一方面,hashtable編碼的集合對象使用字典作為底層實現,字典的每個鍵都是一個字元串對象,每個字元串對象包含了一個集合元素,而字典的值則全部被設置為NULL。
六、t_zset 集合對象類型
- ziplist編碼的壓縮列表對象使用壓縮列表作為底層實現,每個集合元素使用兩個緊挨在一起的壓縮列表節點來保存,第一個節點保存元素的成員(member),而第二個元素則保存元素的分值(score)。
壓縮列表內的集合元素按分值從小到大進行排序,分值較小的元素被放置在靠近表頭的方向,而分值較大的元素則被放置在靠近表尾的方向。
- zset結構中的zsl跳躍表
zsl跳躍表按分值從小到大保存了所有集合元素,每個跳躍表節點都保存了一個集合元素。每個跳躍表節點都保存了一個集合元素:跳躍表節點的object屬性保存了元素的成員,而跳躍表節點的score屬性則保存了元素的分值。通過這個跳躍表,程式可以對有序集合進行範圍型操作。
除此之外,zset結構中的dict字典為有序集合創建了一個從成員到分值的映射,字典中的每個鍵值對都保存了一個集合元素:字典的鍵保存了元素的成員,而字典的值則保存了元素的分值。通過這個字典,程式可以用O(1)複雜度查找給定成員的分值,ZSCORE命令就是根據這一特性實現的,而很多其他有序集合命令都在實現的內部用到了這一特性。
在理論上,有序集合可以單獨使用字典或者跳躍表的其中一種數據結構來實現,但無論單獨使用字典還是跳躍表,在性能上對比起同時使用字典和跳躍表都會有所降低。舉個例子,如果我們只使用字典來實現有序集合,那麼雖然以O(1)複雜度查找成員的分值這一特性會被保留,但是,因為字典以無序的方式來保存集合元素,所以每次在執行範圍型操作——比如ZRANK、ZRANGE等命令時,程式都需要對字典保存的所有元素進行排序,完成這種排序需要至少O(NlogN)時間複雜度,以及額外的O(N)記憶體空間(因為要創建一個數組來保存排序後的元素)。
另一方面,如果我們只使用跳躍表來實現有序集合,那麼跳躍表執行範圍型操作的所有優點都會被保留,但因為沒有了字典,所以根據成員查找分值這一操作的複雜度將從O(1)上升為O(logN)。因為以上原因,為了讓有序集合的查找和範圍型操作都儘可能快地執行,Redis選擇了同時使用字典和跳躍表兩種數據結構來實現有序集合。
七、類型檢查與命令多態
Redis中用於操作鍵的命令基本上可以分為兩種類型。
- 其中一種命令可以對任何類型的鍵執行,比如說DEL命令、EXPIRE命令、RENAME命令、TYPE命令、OBJECT命令等;
- 而另一種命令只能對特定類型的鍵執行,比如說:
❑SET、GET、APPEND、STRLEN等命令只能對字元串鍵執行;
❑HDEL、HSET、HGET、HLEN等命令只能對哈希鍵執行;
❑RPUSH、LPOP、LINSERT、LLEN等命令只能對列表鍵執行;
❑SADD、SPOP、SINTER、SCARD等命令只能對集合鍵執行;
❑ZADD、ZCARD、ZRANK、ZSCORE等命令只能對有序集合鍵執行;