Redis是基于上一篇文章所說的數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了一個對象系統(tǒng),這個系統(tǒng)包含字符串對象残黑、列表對象撇吞、哈希對象、集合對象和有序集合對象這五種類型的對象魄健。
Redis的對象系統(tǒng)還實現(xiàn)了基于引用技術(shù)技術(shù)的內(nèi)存回收機(jī)制,當(dāng)程序不在使用某個對象的時候插勤,這個對象所占用的內(nèi)存就會被自動釋放沽瘦。
對象的類型和編碼
對象結(jié)構(gòu)
typedef struct redisObject {
//類型
unsigned type:4;
//編碼
unsigned encoding:4;
//指向底層實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針
void *ptr;
//
...
}robj;
類型
對象的type屬性記錄了對象的類型农尖,值是五種基礎(chǔ)對象之一析恋。
對于Redis數(shù)據(jù)庫保存對的鍵值對來說,鍵總是一個字符串對象盛卡,而值可以是字符串對象助隧、列表對象、哈希對象窟扑、集合對象或者有序集合對象的其中一種喇颁。
對象 | 對象 type 屬性的值 |
TYPE 命令的輸出 |
---|---|---|
字符串對象 | REDIS_STRING |
"string" |
列表對象 | REDIS_LIST |
"list" |
哈希對象 | REDIS_HASH |
"hash" |
集合對象 | REDIS_SET |
"set" |
有序集合對象 | REDIS_ZSET |
"zset" |
使用Type命令可以返回數(shù)據(jù)庫鍵對應(yīng)的值對象的類型:
#鍵為字符串對象,值為字符串對象
redis> SET msg "hello world"
OK
redis> TYPE msg
string
#鍵為字符串對象嚎货,值為列表對象
redis>RPUSH numbers 1 3 5
(integer) 6
redis>TYPE numbers
list
·····
編碼和底層實現(xiàn)
對象的ptr指針指向?qū)ο蟮牡讓訉崿F(xiàn)數(shù)據(jù)結(jié)構(gòu)橘霎,而這些數(shù)據(jù)結(jié)構(gòu)由對象的encoding屬性決定。
encoding屬性記錄了對象所使用的編碼殖属,也即是說這個對象使用了什么數(shù)據(jù)結(jié)構(gòu)作為對象的底層實現(xiàn)姐叁。
每種類型的對象都至少使用了兩種不同的編碼。
類型 | 編碼 | 對象 |
---|---|---|
REDIS_STRING |
REDIS_ENCODING_INT |
使用整數(shù)值實現(xiàn)的字符串對象洗显。 |
REDIS_STRING |
REDIS_ENCODING_EMBSTR |
使用 embstr 編碼的簡單動態(tài)字符串實現(xiàn)的字符串對象外潜。 |
REDIS_STRING |
REDIS_ENCODING_RAW |
使用簡單動態(tài)字符串實現(xiàn)的字符串對象。 |
REDIS_LIST |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現(xiàn)的列表對象挠唆。 |
REDIS_LIST |
REDIS_ENCODING_LINKEDLIST |
使用雙端鏈表實現(xiàn)的列表對象处窥。 |
REDIS_HASH |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現(xiàn)的哈希對象。 |
REDIS_HASH |
REDIS_ENCODING_HT |
使用字典實現(xiàn)的哈希對象玄组。 |
REDIS_SET |
REDIS_ENCODING_INTSET |
使用整數(shù)集合實現(xiàn)的集合對象滔驾。 |
REDIS_SET |
REDIS_ENCODING_HT |
使用字典實現(xiàn)的集合對象谒麦。 |
REDIS_ZSET |
REDIS_ENCODING_ZIPLIST |
使用壓縮列表實現(xiàn)的有序集合對象。 |
REDIS_ZSET |
REDIS_ENCODING_SKIPLIST |
使用跳躍表和字典實現(xiàn)的有序集合對象哆致。 |
使用 OBJECT ENCODING 命令可以查看一個數(shù)據(jù)庫鍵的值對象的編碼:
redis> SET msg "hello wordl"
OK
redis> OBJECT ENCODING msg
"embstr"
redis> SET story "long long long long long long ago ..."
OK
redis> OBJECT ENCODING story
"raw"
redis> SADD numbers 1 3 5
(integer) 3
redis> OBJECT ENCODING numbers
"intset"
····
字符串對象
字符串對象的編碼可以是int绕德、raw或者embstr
如果一個字符串對象保存的是整數(shù)值,并且這個整數(shù)值可以用long類型來表示摊阀,那么字符串對象會將整數(shù)值保存在字符串對象結(jié)構(gòu)的ptr屬性里面耻蛇。
如果字符串對象保存的是一個字符串值,并且這個字符串值的長度大于32字節(jié)胞此,那么字符串對象將使用一個簡單動態(tài)字符串(SDS)來保存這個字符串值臣咖,并將對象的編碼設(shè)置為raw。
如果字符串對象保存的是一個字符串值豌鹤,并且這個字符串值的長度小于等于32字節(jié)亡哄,那么字符串對象將使用embstr編碼的方式來保存這個字符串值。
embstr編碼是專門用于和保存短字符串的一種優(yōu)化編碼方式布疙,這種編碼和raw編碼一樣,都是用redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu)來表示字符串對象愿卸,但raw編碼會調(diào)用兩次內(nèi)存分配函數(shù)來分別創(chuàng)建redisObject結(jié)構(gòu)和sdshdr結(jié)構(gòu)灵临,而embstr編碼則通過調(diào)用一次內(nèi)存分配函數(shù)來分配一塊連續(xù)的空間,空間中依次包含redisObject和sdshdr兩個結(jié)構(gòu)趴荸。
列表對象
列表對象的編碼可以是ziplist或者linkedlist儒溉。
ziplist編碼的列表對象使用壓縮列表作為底層實現(xiàn),每個壓縮列表節(jié)點(entry)保存了一個列表元素发钝。
linkedlist編碼的列表對象使用雙端鏈表作為底層實現(xiàn)顿涣,每個雙端鏈表節(jié)點(node)都保存了一個字符串對象,而每個字符串對象都保存了一個列表元素酝豪。
linkdedlist編碼的列表對象在底層的雙端鏈表結(jié)構(gòu)中包含了多個字符串對象涛碑,這種嵌套字符串對象的行為在哈希對象、集合對象和有序結(jié)合對象中都會出現(xiàn)孵淘,字符串對象是Redis五種類型的對象中唯一一種會被其它四中類型對象嵌套的對象蒲障。
哈希對象
哈希對象的編碼可以是ziplist或者h(yuǎn)ashtable。
ziplist編碼的哈希對象使用壓縮列表作為底層實現(xiàn)瘫证,每當(dāng)有新的鍵值對要加入到哈希對象時揉阎,程序會先將保存了鍵的壓縮列表節(jié)點推入到壓縮列表表尾,然后再將保存了值的壓縮列表節(jié)點推入到壓縮列表表尾背捌。
hashtable編碼的哈希對象使用了字典作為底層實現(xiàn)毙籽,哈希對象中的每個鍵值對都使用一個字典鍵值對來保存:
- 字典的每個鍵都是一個字符串對象,對象中保存了鍵值對的鍵毡庆;
- 字典的每個值都是一個字符串對象坑赡,對象中保存了鍵值對的值烙如。
集合對象
結(jié)合對象的編碼可以是intset或者h(yuǎn)ashtable。
intset編碼的集合對象使用整數(shù)集合作為底層實現(xiàn)垮衷,集合對象包含的所有元素都被保存在整數(shù)集合里面厅翔。
hashtable編碼的集合對象使用字典作為底層實現(xiàn),字典的每個鍵都是一個字符串對象搀突,每個字符串對象包含了一個集合元素刀闷,而字典的值則全部被設(shè)置為NULL。
有序集合對象
有序集合的編碼可以是ziplist或者skiplist仰迁。
ziplist編碼的壓縮列表對象使用壓縮列表作為底層實現(xiàn)甸昏,每個集合元素使用兩個緊挨在一起的壓縮列表節(jié)點來保存,第一個節(jié)點保存元素的成員(member)徐许,而第二個元素則保存元素的分值(score)施蜜。
壓縮列表內(nèi)的集合元素按分值從小到大進(jìn)行排序,分值較小的元素被放置在靠近表頭的位置雌隅,而分值較大的元素則被放置在靠近表尾的方向翻默。
skiplist編碼的有序結(jié)合對象使用zset結(jié)構(gòu)作為底層實現(xiàn),一個zset結(jié)構(gòu)包含一個字典和一個跳躍表:
typedef struct zset{
zskiplist *zsl;
dict *dict;
}zset;
zset結(jié)構(gòu)中的zsl跳躍表按分值從小到大保存了所有集合元素恰起,每個跳躍表節(jié)點都保存了一個集合元素:跳躍表節(jié)點的object屬性保存了元素的成員修械,而跳躍表節(jié)點的score屬性則保存了元素的分值。
通過這個跳躍表检盼,程序可以對有序集合進(jìn)行范圍型操作肯污。、
除此之外吨枉,zset結(jié)構(gòu)中的dict字典為有序集合創(chuàng)建了一個從成員到分值的映射蹦渣,字典中的每個鍵值對都保存了一個集合元素:字典的鍵保存了元素的成員,而字典的值則保存了元素的分值貌亭。
通過這個字典柬唯,程序可以用O(1)復(fù)雜度查找給定成員的分值。
zset結(jié)構(gòu)同時使用跳躍表和字典來保存有序集合元素属提,但這兩種數(shù)據(jù)結(jié)構(gòu)都會通過指針來共享相同元素的成員和分值权逗,所以同時使用跳躍表和字典來保存集合元素不會產(chǎn)生任何重復(fù)成員或者分值,也不會因此而浪費額外的內(nèi)存冤议。
編碼與轉(zhuǎn)換
在每一種對象中斟薇,都會存在編碼和轉(zhuǎn)換,個人的理解是在滿足特定條件下恕酸,對象底層的編碼方式會從這種轉(zhuǎn)換為另一種堪滨。
舉些例子:
在字符串對象中:
int編碼的字符串對象和embstr編碼的字符串對象在條件滿足的情況下,會被轉(zhuǎn)換成raw編碼的字符串對象蕊温。
如果通過APPEND命令袱箱,向一個保存了整數(shù)值的字符串對象追加了一個字符串值遏乔,因為追加操作只能對字符串值執(zhí)行,所以程序會先將之前保存的整數(shù)值轉(zhuǎn)換成字符串值发笔,然后再執(zhí)行追加操作盟萨,操作的執(zhí)行結(jié)果就是一個raw編碼的保存了字符串值的字符串對象。
在哈希對象中:
當(dāng)哈希對象可以同時滿足以下兩個條件時了讨,哈希對象使用ziplist編碼:
- 哈希對象保存的所有鍵值對的鍵和值的字符串長度都小于64字節(jié)捻激;
- 哈希對象保存的鍵值對數(shù)量小于512個;
不能滿足這兩個條件的哈希對象需要使用hashtable編碼前计;
還有關(guān)于每個對象API在上一篇文章講過胞谭,根據(jù)每個對象的特性,用于保存相應(yīng)的數(shù)據(jù)上男杈。
Redis還有很多需要學(xué)習(xí)丈屹,關(guān)于單機(jī)和集群、事務(wù)伶棒、持久化等都還沒了解到旺垒,但是底層了解之后就是用于實戰(zhàn),下一篇文章將會在實際項目中使用Redis