摘要:對象源碼結(jié)構(gòu)如下對象類型對象編碼引用統(tǒng)計指向底層實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針字段對象類型,就是我們常說的。。對象編碼對應(yīng)跳躍表壓縮列表集合動態(tài)字符串等八種底層數(shù)據(jù)結(jié)構(gòu)。
相信很多人應(yīng)該都知道 Redis 有五種數(shù)據(jù)類型:字符串、列表、哈希、集合和有序集合。但這五種數(shù)據(jù)類型是什么含義?Redis 的數(shù)據(jù)又是怎樣存儲的?今天我們一起來認(rèn)識下 Redis 這五種數(shù)據(jù)結(jié)構(gòu)的含義及其底層實現(xiàn)。
首先要明確的是,Redis 并沒有直接使用這五種數(shù)據(jù)結(jié)構(gòu)來實現(xiàn)鍵值對數(shù)據(jù)庫,而是基于這些數(shù)據(jù)結(jié)構(gòu)創(chuàng)建了一套對象系統(tǒng),我們常說的數(shù)據(jù)類型,準(zhǔn)確來說,是 Redis 對象系統(tǒng)的類型。
1 對象對于 Redis 而言,所有鍵值對的存儲,都是將數(shù)據(jù)存儲在對象結(jié)構(gòu)中。所不同的是,鍵總是一個字符串對象,值可以是任意類型的對象。
對象源碼結(jié)構(gòu)如下:
typedef struct redisObject { unsigned type:4; // 對象類型 unsigned encoding:4; // 對象編碼 unsigned lru:LRU_BITS; // LRU int refcount; // 引用統(tǒng)計 void *ptr; // 指向底層實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針 } robj;
type 字段:對象類型,就是我們常說的。string、list、hash、set、zset。
encoding:對象編碼。也就是我們上面說的底層數(shù)據(jù)結(jié)構(gòu)。
LRU:鍵值對的 LRU。
refcount:鍵值對對象的引用統(tǒng)計。當(dāng)此值為 0 時,回收對象。
*ptr:指向底層實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針。就是實際存放數(shù)據(jù)的地址。
1.2 對象類型對象有五種數(shù)據(jù)類型,就是我們上面提過的:
字符串類型
列表類型
哈希類型
集合類型
有序集合類型
結(jié)合我們上面提到的鍵值對存儲類型的差別,可以了解到,我們常說的“一個列表鍵或一個哈希鍵”,本質(zhì)上指的是:一個 key 對應(yīng)的 value 是列表對象或哈希對象。
對于 type 字段,我們可以使用 TYPE 命令來查看指定 key 對應(yīng) value 值的對象類型。
按道理講,已經(jīng)有了 type,為什么還要搞個編碼呢?
想想看,通過 encoding 屬性,我們是不是使用不同編碼的對象?這種使用方式可以根據(jù)不同的使用場景來為一個對象設(shè)置不同的編碼,從而優(yōu)化在某一場景下的效率,極大的提升了 Redis 的靈活性和效率。
舉個栗子,在列表對象包含的元素比較少時,Redis 使用壓縮列表作為列表對象的底層實現(xiàn):
壓縮列表比快速鏈表更節(jié)約內(nèi)存,并且在元素數(shù)量較少時,在內(nèi)存中以連續(xù)塊方式報錯的壓縮列表比起快速列表可以更快的載入到緩存中;
隨著列表對象包含的元素越來越多,使用壓縮列表保存元素的優(yōu)勢消失時,對象就會將底層實現(xiàn)從壓縮列表轉(zhuǎn)為功能更強、也更適合保存大量元素的快速鏈表。
后面介紹完編碼類型后,我們會詳細(xì)認(rèn)識不同類型對應(yīng)的各個編碼方式。
encoding 屬性有以下取值:
OBJ_ENCODING_RAW
OBJ_ENCODING_INT
OBJ_ENCODING_HT
OBJ_ENCODING_QUICKLIST
OBJ_ENCODING_ZIPLIST
OBJ_ENCODING_INTSET
OBJ_ENCODING_SKIPLIST
OBJ_ENCODING_EMBSTR
對象的編碼類型可以由 OBJECT ENCODING 命令獲取。
OBJECT ENCODING 命令對應(yīng)源碼如下:
# src/object.c char *strEncoding(int encoding) { switch(encoding) { case OBJ_ENCODING_RAW: return "raw"; case OBJ_ENCODING_INT: return "int"; case OBJ_ENCODING_HT: return "hashtable"; case OBJ_ENCODING_QUICKLIST: return "quicklist"; case OBJ_ENCODING_ZIPLIST: return "ziplist"; case OBJ_ENCODING_INTSET: return "intset"; case OBJ_ENCODING_SKIPLIST: return "skiplist"; case OBJ_ENCODING_EMBSTR: return "embstr"; default: return "unknown"; } }
OBJECT ENCODING 命令輸出值與 encoding 屬性取值對應(yīng)關(guān)系如下:
對象使用的底層數(shù)據(jù)結(jié)構(gòu) | 編碼常量 | OBJECT ENCODING 輸出 |
---|---|---|
簡單動態(tài)字符串 | OBJ_ENCODING_RAW | "raw" |
整數(shù) | OBJ_ENCODING_INT | "int" |
embstr 編碼的簡單動態(tài)字符串 | OBJ_ENCODING_EMBSTR | "embstr" |
字典 | OBJ_ENCODING_HT | "hashtable" |
壓縮列表 | OBJ_ENCODING_ZIPLIST | "ziplist" |
快速列表 | OBJ_ENCODING_QUICKLIST | "quicklist" |
整數(shù)集合 | OBJ_ENCODING_INTSET | "intset" |
跳躍表 | OBJ_ENCODING_SKIPLIST | "skiplist" |
總結(jié)來看,如下圖:
十一種不同編碼的對象分別是:
使用雙端或快速列表實現(xiàn)的列表對象
使用壓縮列表實現(xiàn)的列表對象
使用字典實現(xiàn)的哈希對象
使用壓縮列表實現(xiàn)的哈希對象
使用字典實現(xiàn)的集合對象
使用整數(shù)集合實現(xiàn)的集合對象
使用壓縮列表實現(xiàn)的有序集合對象
使用跳躍表實現(xiàn)的有序集合對象
使用普通 SDS 實現(xiàn)的字符串對象
使用 embstr 編碼的 SDS 實現(xiàn)的字符串對象
使用整數(shù)值實現(xiàn)的字符串對象
接下來,我們將對上述十一種對象一一介紹。之后再一一認(rèn)識對象編碼。
2 字符串對象字符串對象的可選編碼分別是:int、raw 或者 embstr。
2.1 int 編碼的字符串對象如果一個字符串對象保存的是整數(shù)值,并且這個整數(shù)值可以用 long 類型表示,那么字符串對象會將整數(shù)值保存在字符串對象結(jié)構(gòu)的 ptr 屬性中,并將字符串對象的編碼設(shè)置為 int。
我們執(zhí)行以下 SET 命令,服務(wù)器將創(chuàng)建一個如下圖所示的 int 編碼的字符串對象作為 num 鍵的值:
# redis-cli 127.0.0.1:6380> set num 12345 OK 127.0.0.1:6380> OBJECT ENCODING num "int"2.2 raw 編碼的字符串對象
如果字符串對象保存的是一個字符串值,并且這個字符串值的長度大于 44 字節(jié)(根據(jù)版本的不同,這個值會有差異。詳見 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那么字符串對象將使用**簡單動態(tài)字符串(SDS)來保存這個字符串值,并將對象的編碼設(shè)置為 raw。
我們執(zhí)行下面的 SET 命令,服務(wù)器將創(chuàng)建一個圖 7 所示的 raw 編碼的字符串對象作為 k1 鍵的值(45 字節(jié)):
127.0.0.1:7379> set story "k01234567890123456789012345678901234567890123" OK 127.0.0.1:7379> OBJECT ENCODING k4 "raw"2.3 embstr 編碼的字符串對象
如果字符串保存的是一個字符串值,并且這個字符串值的長度小于等于 44 字節(jié)(根據(jù)版本的不同,這個值會有差異。詳見 object.c 文件中的 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 常量),那么字符串對象將使用 embstr 編碼的方式來保存這個字符串。
embstr 編碼是專門用于保存段字符串的一種優(yōu)化編碼方式,這種編碼和 raw 編碼一樣,都使用 redisObject 和 sdshdr 結(jié)構(gòu)來表示字符串對象。但和 raw 編碼的字符串對象不同的是:
raw 編碼會調(diào)用兩次內(nèi)存分配函數(shù)來分別創(chuàng)建 redisObject 和 sdshdr 結(jié)構(gòu)
embstr 編碼通過一次內(nèi)存分配函數(shù)分配一塊連續(xù)的空間,空間中依次包含 redisObject 和 sdsHdr 兩個結(jié)構(gòu)。
相對應(yīng)的,釋放內(nèi)存時,embstr 編碼的對象也只需調(diào)用一次內(nèi)存釋放函數(shù)。
因此,使用 embstr 編碼的字符串對象來保存短字符串值有以下好處:
創(chuàng)建字符串對象時,內(nèi)存分配次數(shù)從兩次降低為一次。
釋放 embstr 編碼的字符串對象時,調(diào)用內(nèi)存釋放函數(shù)的次數(shù)從兩次降低為一次。
更好地利用緩存優(yōu)勢。embstr 編碼的字符串對象的所有數(shù)據(jù)都保存在一塊連續(xù)的內(nèi)存中 ,這種方式比 raw 編碼的字符串對象能夠更好的利用緩存帶來的優(yōu)勢。
以下命令創(chuàng)建了一個 embstr 編碼的字符串對象作為 msg 鍵的值,值對象結(jié)構(gòu)如圖 8。
127.0.0.1:6380> SET msg hello OK 127.0.0.1:6380> OBJECT ENCODING msg "embstr"2.4 浮點數(shù)編碼
Redis 中,long double 類型的浮點數(shù)也是作為字符串值來保存的。
我們要保存一個浮點數(shù)到字符串對象中,程序會先將這個浮點數(shù)轉(zhuǎn)換成字符串值,然后再保存轉(zhuǎn)換所得的字符串值。
執(zhí)行以下代碼,將創(chuàng)建一個包含 3.14 的字符串表示 "3.14" 的字符串對象:
127.0.0.1:6380> SET pi 3.14 OK 127.0.0.1:6380> OBJECT ENCODING pi "embstr"
在有需要的時候,程序會將保存在字符串對象里的字符串值轉(zhuǎn)換成浮點數(shù)值,執(zhí)行某些操作,然后將所得的浮點數(shù)值轉(zhuǎn)換回字符串值,繼續(xù)保存在字符串對象中。
比如,我們對 pi 鍵執(zhí)行以下操作:
127.0.0.1:6380> INCRBYFLOAT pi 2.0 "5.14" 127.0.0.1:6380> OBJECT ENCODING pi "embstr"
執(zhí)行 INCRBYFLOAT 命令過程中,實際上就會出現(xiàn)字符串與浮點數(shù)值互相轉(zhuǎn)換的情況。
2.5 編碼轉(zhuǎn)換int 編碼的字符串對象和 embstr 編碼的字符串對象在滿足某些條件的情況下,會被轉(zhuǎn)換為 raw 編碼的字符串對象。
對于 int 編碼的字符串對象來說,如果我們在執(zhí)行命令后,使得這個對象保存的不再是整數(shù)值,而是一個字符串,那么字符串對象就會從 int 變?yōu)?raw。比如 APPEND 命令等。
另外,對于 embstr 編碼的字符串,由于 Redis 沒有為其編寫任何相應(yīng)的修改程序,所以 embstr 編碼的字符串對象實際上是只讀的。當(dāng)我們對 embstr 編碼的字符串對象執(zhí)行任何修改命令時,程序都會先將對象的編碼從 embstr 轉(zhuǎn)換成 raw。也就是說,embstr 編碼的字符串一旦修改,一定會轉(zhuǎn)換成 raw 編碼的字符串對象。
2.6 值與編碼對應(yīng)關(guān)系對于字符串對象各個編碼的情況,總結(jié)如下:
值 | 編碼 |
---|---|
可以用 long 表示的整數(shù)值 | int |
可以用 long double 保存的浮點數(shù) | raw 或 embstr |
不可以用 long 或 long double 表示的整數(shù)或小數(shù)值 | raw 或 embstr |
大于 44 字節(jié)的字符串 | raw |
小于或等于 44 字節(jié)的字符串 | embstr |
列表對象的可選編碼分別是:quicklist(3.2 版本前是 ziplist 和 linkedlist)。
3.1 quicklist 編碼的列表對象3.2 版本引入了 quicklist 編碼,此編碼結(jié)合了 ziplist 和 linkedlist,使用雙向鏈表的形式,在每個節(jié)點上存儲一個 ziplist,而每個 ziplist 又可以存儲多個鍵值對。也就是說,quicklist 每個節(jié)點上存儲的不是一個數(shù)據(jù),而是一片數(shù)據(jù)。
執(zhí)行以下命令,服務(wù)器將會創(chuàng)建一個列表對象,quicklist 結(jié)構(gòu)如圖 8 所示:
127.0.0.1:7379> RPUSH animal "dog" "cat" "pig" (integer) 3 (5.12s) 127.0.0.1:7379> OBJECT ENCODING animal "quicklist"總結(jié)
Redis 自己實現(xiàn)了一套對象系統(tǒng)來實現(xiàn)所有功能。
對象有對象類型和對象編碼。
對象類型對應(yīng)字符串、列表、哈希、集合、有序集合五種。
對象編碼對應(yīng)跳躍表、壓縮列表、集合、動態(tài)字符串等八種底層數(shù)據(jù)結(jié)構(gòu)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/62139.html
摘要:哈希對象哈希對象的可選編碼分別是和。編碼的哈希對象編碼的哈希對象使用壓縮列表作為底層實現(xiàn)。關(guān)于哈希編碼轉(zhuǎn)換的函數(shù),可以參考,源碼如下是原始對象,是目標(biāo)編碼。對應(yīng)源碼如下對象元素數(shù)量為,或者總結(jié)哈希對象有和編碼。 繼續(xù)擼我們的對象和數(shù)據(jù)類型。 上節(jié)我們一起認(rèn)識了字符串和列表,接下來還有哈希、集合和有序集合。 1 哈希對象 哈希對象的可選編碼分別是:ziplist 和 hashtable。...
摘要:沒有直接使用語言傳統(tǒng)的字符串表示以空字符串結(jié)尾的字符數(shù)組,而是構(gòu)建了一種名為簡單動態(tài)字符串的抽象類型,并將用作的默認(rèn)字符串表示。對比字符串,有幾大優(yōu)點常數(shù)復(fù)雜度獲取字符串長度杜絕緩沖區(qū)溢出減少修改字符串時所需的內(nèi)存重分配次數(shù)。 Redis 沒有直接使用 C 語言傳統(tǒng)的字符串表示(以空字符串結(jié)尾的字符數(shù)組),而是構(gòu)建了一種名為簡單動態(tài)字符串(simple dynamic string)的...
摘要:此時服務(wù)器處于休眠狀態(tài),并使用進(jìn)行事件輪詢,等待監(jiān)聽事件的發(fā)生。繼續(xù)執(zhí)行被調(diào)試程序,直至下一個斷點或程序結(jié)束縮寫。服務(wù)啟動包括初始化基礎(chǔ)配置數(shù)據(jù)結(jié)構(gòu)對外提供服務(wù)的準(zhǔn)備工作還原數(shù)據(jù)庫執(zhí)行事件循環(huán)等。 一直很羨慕那些能讀 Redis 源碼的童鞋,也一直想自己解讀一遍,但迫于 C 大魔王的壓力,解讀日期遙遙無期。 相信很多小伙伴應(yīng)該也都對或曾對源碼感興趣,但一來覺得自己不會 C 語言,二來也...
閱讀 2473·2021-11-24 09:39
閱讀 3405·2021-11-15 11:37
閱讀 2251·2021-10-08 10:04
閱讀 3965·2021-09-09 11:54
閱讀 1883·2021-08-18 10:24
閱讀 1032·2019-08-30 11:02
閱讀 1793·2019-08-29 18:45
閱讀 1651·2019-08-29 16:33