詳解Redis數(shù)據(jù)類(lèi)型實(shí)現(xiàn)原理
1. 對(duì)象的類(lèi)型與編碼
Redis使用前面說(shuō)的五大數(shù)據(jù)類(lèi)型來(lái)表示鍵和值,每次在Redis數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)鍵值對(duì)時(shí),至少會(huì)創(chuàng)建兩個(gè)對(duì)象,一個(gè)是鍵對(duì)象,一個(gè)是值對(duì)象,而Redis中的每個(gè)對(duì)象都是由 redisObject 結(jié)構(gòu)來(lái)表示:
typedef struct redisObject{ //類(lèi)型 unsigned type:4; //編碼 unsigned encoding:4; //指向底層數(shù)據(jù)結(jié)構(gòu)的指針 void *ptr; //引用計(jì)數(shù) int refcount; //記錄最后一次被程序訪(fǎng)問(wèn)的時(shí)間 unsigned lru:22; }robj
① type屬性
對(duì)象的type屬性記錄了對(duì)象的類(lèi)型,這個(gè)類(lèi)型就是前面講的五大數(shù)據(jù)類(lèi)型:
可以通過(guò)如下命令來(lái)判斷對(duì)象類(lèi)型:
type key
注意:在Redis中,鍵總是一個(gè)字符串對(duì)象,而值可以是字符串、列表、集合等對(duì)象,所以我們通常說(shuō)的鍵為字符串鍵,表示的是這個(gè)鍵對(duì)應(yīng)的值為字符串對(duì)象,我們說(shuō)一個(gè)鍵為集合鍵時(shí),表示的是這個(gè)鍵對(duì)應(yīng)的值為集合對(duì)象。
② encoding 屬性和 *prt 指針
對(duì)象的 prt 指針指向?qū)ο蟮讓拥臄?shù)據(jù)結(jié)構(gòu),而數(shù)據(jù)結(jié)構(gòu)由 encoding 屬性來(lái)決定。
而每種類(lèi)型的對(duì)象都至少使用了兩種不同的編碼:
可以通過(guò)如下命令查看值對(duì)象的編碼:
OBJECT ENCODING key
比如 string 類(lèi)型:
2. 字符串對(duì)象
字符串是Redis最基本的數(shù)據(jù)類(lèi)型,不僅所有key都是字符串類(lèi)型,其它幾種數(shù)據(jù)類(lèi)型構(gòu)成的元素也是字符串。注意字符串的長(zhǎng)度不能超過(guò)512M。
① 編碼
字符串對(duì)象的編碼可以是int,raw或者embstr。
1、int 編碼:保存的是可以用 long 類(lèi)型表示的整數(shù)值。
2、raw 編碼:保存長(zhǎng)度大于44字節(jié)的字符串。
3、embstr 編碼:保存長(zhǎng)度小于44字節(jié)的字符串。、
由上可以看出,int 編碼是用來(lái)保存整數(shù)值,raw編碼是用來(lái)保存長(zhǎng)字符串,而embstr是用來(lái)保存短字符串。其實(shí) embstr 編碼是專(zhuān)門(mén)用來(lái)保存短字符串的一種優(yōu)化編碼,raw 和 embstr 的區(qū)別:、
embstr與raw都使用redisObject和sds保存數(shù)據(jù),區(qū)別在于,embstr的使用只分配一次內(nèi)存空間(因此redisObject和sds是連續(xù)的),而raw需要分配兩次內(nèi)存空間(分別為redisObject和sds分配空間)。因此與raw相比,embstr的好處在于創(chuàng)建時(shí)少分配一次空間,刪除時(shí)少釋放一次空間,以及對(duì)象的所有數(shù)據(jù)連在一起,尋找方便。而embstr的壞處也很明顯,如果字符串的長(zhǎng)度增加需要重新分配內(nèi)存時(shí),整個(gè)redisObject和sds都需要重新分配空間,因此redis中的embstr實(shí)現(xiàn)為只讀。
ps:Redis中對(duì)于浮點(diǎn)數(shù)類(lèi)型也是作為字符串保存的,在需要的時(shí)候再將其轉(zhuǎn)換成浮點(diǎn)數(shù)類(lèi)型。
② 編碼的轉(zhuǎn)換
當(dāng) int 編碼保存的值不再是整數(shù),或大小超過(guò)了long的范圍時(shí),自動(dòng)轉(zhuǎn)化為raw。
對(duì)于 embstr 編碼,由于 Redis 沒(méi)有對(duì)其編寫(xiě)任何的修改程序(embstr 是只讀的),在對(duì)embstr對(duì)象進(jìn)行修改時(shí),都會(huì)先轉(zhuǎn)化為raw再進(jìn)行修改,因此,只要是修改embstr對(duì)象,修改后的對(duì)象一定是raw的,無(wú)論是否達(dá)到了44個(gè)字節(jié)。
3. 列表對(duì)象
list 列表,它是簡(jiǎn)單的字符串列表,按照插入順序排序,你可以添加一個(gè)元素到列表的頭部(左邊)或者尾部(右邊),它的底層實(shí)際上是個(gè)鏈表結(jié)構(gòu)。
① 編碼
列表對(duì)象的編碼可以是 ziplist(壓縮列表) 和 linkedlist(雙端鏈表)。
比如我們執(zhí)行以下命令,創(chuàng)建一個(gè) key = ‘numbers',value = ‘1 three 5' 的三個(gè)值的列表。
rpush numbers 1 "three" 5
ziplist 編碼表示如下
linkedlist表示如下:
② 編碼轉(zhuǎn)換
當(dāng)同時(shí)滿(mǎn)足下面兩個(gè)條件時(shí),使用ziplist(壓縮列表)編碼:
1、列表保存元素個(gè)數(shù)小于512個(gè)
2、每個(gè)元素長(zhǎng)度小于64字節(jié)
不能滿(mǎn)足這兩個(gè)條件的時(shí)候使用 linkedlist 編碼。
上面兩個(gè)條件可以在redis.conf 配置文件中的 list-max-ziplist-value選項(xiàng)和 list-max-ziplist-entries 選項(xiàng)進(jìn)行配置。
4. 哈希對(duì)象
哈希對(duì)象的鍵是一個(gè)字符串類(lèi)型,值是一個(gè)鍵值對(duì)集合。
① 編碼
哈希對(duì)象的編碼可以是 ziplist 或者 hashtable。
當(dāng)使用ziplist,也就是壓縮列表作為底層實(shí)現(xiàn)時(shí),新增的鍵值對(duì)是保存到壓縮列表的表尾。比如執(zhí)行以下命令:
hset profile name "Tom" hset profile age 25 hset profile career "Programmer"
如果使用ziplist,profile 存儲(chǔ)如下:
當(dāng)使用 hashtable 編碼時(shí),上面命令存儲(chǔ)如下:
hashtable 編碼的哈希表對(duì)象底層使用字典數(shù)據(jù)結(jié)構(gòu),哈希對(duì)象中的每個(gè)鍵值對(duì)都使用一個(gè)字典鍵值對(duì)。
在前面介紹壓縮列表時(shí),我們介紹過(guò)壓縮列表是Redis為了節(jié)省內(nèi)存而開(kāi)發(fā)的,是由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型數(shù)據(jù)結(jié)構(gòu),相對(duì)于字典數(shù)據(jù)結(jié)構(gòu),壓縮列表用于元素個(gè)數(shù)少、元素長(zhǎng)度小的場(chǎng)景。其優(yōu)勢(shì)在于集中存儲(chǔ),節(jié)省空間。
② 編碼轉(zhuǎn)換
和上面列表對(duì)象使用 ziplist 編碼一樣,當(dāng)同時(shí)滿(mǎn)足下面兩個(gè)條件時(shí),使用ziplist(壓縮列表)編碼:
1、列表保存元素個(gè)數(shù)小于512個(gè)
2、每個(gè)元素長(zhǎng)度小于64字節(jié)
不能滿(mǎn)足這兩個(gè)條件的時(shí)候使用 hashtable 編碼。第一個(gè)條件可以通過(guò)配置文件中的 set-max-intset-entries 進(jìn)行修改。
5. 集合對(duì)象
集合對(duì)象 set 是 string 類(lèi)型(整數(shù)也會(huì)轉(zhuǎn)換成string類(lèi)型進(jìn)行存儲(chǔ))的無(wú)序集合。注意集合和列表的區(qū)別:集合中的元素是無(wú)序的,因此不能通過(guò)索引來(lái)操作元素;集合中的元素不能有重復(fù)。
① 編碼
集合對(duì)象的編碼可以是 intset 或者 hashtable。
intset 編碼的集合對(duì)象使用整數(shù)集合作為底層實(shí)現(xiàn),集合對(duì)象包含的所有元素都被保存在整數(shù)集合中。
hashtable 編碼的集合對(duì)象使用 字典作為底層實(shí)現(xiàn),字典的每個(gè)鍵都是一個(gè)字符串對(duì)象,這里的每個(gè)字符串對(duì)象就是一個(gè)集合中的元素,而字典的值則全部設(shè)置為 null。這里可以類(lèi)比Java集合中HashSet 集合的實(shí)現(xiàn),HashSet 集合是由 HashMap 來(lái)實(shí)現(xiàn)的,集合中的元素就是 HashMap 的key,而 HashMap 的值都設(shè)為 null。
SADD numbers 1 3 5
SADD Dfruits "apple" "banana" "cherry"
② 編碼轉(zhuǎn)換
當(dāng)集合同時(shí)滿(mǎn)足以下兩個(gè)條件時(shí),使用 intset 編碼:
1、集合對(duì)象中所有元素都是整數(shù)
2、集合對(duì)象所有元素?cái)?shù)量不超過(guò)512
不能滿(mǎn)足這兩個(gè)條件的就使用 hashtable 編碼。第二個(gè)條件可以通過(guò)配置文件的 set-max-intset-entries 進(jìn)行配置。
6. 有序集合對(duì)象
和上面的集合對(duì)象相比,有序集合對(duì)象是有序的。與列表使用索引下標(biāo)作為排序依據(jù)不同,有序集合為每個(gè)元素設(shè)置一個(gè)分?jǐn)?shù)(score)作為排序依據(jù)。
① 編碼
有序集合的編碼可以是 ziplist 或者 skiplist。
ziplist 編碼的有序集合對(duì)象使用壓縮列表作為底層實(shí)現(xiàn),每個(gè)集合元素使用兩個(gè)緊挨在一起的壓縮列表節(jié)點(diǎn)來(lái)保存,第一個(gè)節(jié)點(diǎn)保存元素的成員,第二個(gè)節(jié)點(diǎn)保存元素的分值。并且壓縮列表內(nèi)的集合元素按分值從小到大的順序進(jìn)行排列,小的放置在靠近表頭的位置,大的放置在靠近表尾的位置。
ZADD price 8.5 apple 5.0 banana 6.0 cherry
skiplist 編碼的有序集合對(duì)象使用 zet 結(jié)構(gòu)作為底層實(shí)現(xiàn),一個(gè) zset 結(jié)構(gòu)同時(shí)包含一個(gè)字典和一個(gè)跳躍表:
typedef struct zset{ //跳躍表 zskiplist *zsl; //字典 dict *dice; } zset;
字典的鍵保存元素的值,字典的值則保存元素的分值;跳躍表節(jié)點(diǎn)的 object 屬性保存元素的成員,跳躍表節(jié)點(diǎn)的 score 屬性保存元素的分值。
這兩種數(shù)據(jù)結(jié)構(gòu)會(huì)通過(guò)指針來(lái)共享相同元素的成員和分值,所以不會(huì)產(chǎn)生重復(fù)成員和分值,造成內(nèi)存的浪費(fèi)。
說(shuō)明:其實(shí)有序集合單獨(dú)使用字典或跳躍表其中一種數(shù)據(jù)結(jié)構(gòu)都可以實(shí)現(xiàn),但是這里使用兩種數(shù)據(jù)結(jié)構(gòu)組合起來(lái),原因是假如我們單獨(dú)使用 字典,雖然能以 O(1) 的時(shí)間復(fù)雜度查找成員的分值,但是因?yàn)樽值涫且詿o(wú)序的方式來(lái)保存集合元素,所以每次進(jìn)行范圍操作的時(shí)候都要進(jìn)行排序;假如我們單獨(dú)使用跳躍表來(lái)實(shí)現(xiàn),雖然能執(zhí)行范圍操作,但是查找操作有 O(1)的復(fù)雜度變?yōu)榱薕(logN)。因此Redis使用了兩種數(shù)據(jù)結(jié)構(gòu)來(lái)共同實(shí)現(xiàn)有序集合。
② 編碼轉(zhuǎn)換
當(dāng)有序集合對(duì)象同時(shí)滿(mǎn)足以下兩個(gè)條件時(shí),對(duì)象使用 ziplist 編碼:
1、保存的元素?cái)?shù)量小于128;
2、保存的所有元素長(zhǎng)度都小于64字節(jié)。
不能滿(mǎn)足上面兩個(gè)條件的使用 skiplist 編碼。以上兩個(gè)條件也可以通過(guò)Redis配置文件zset-max-ziplist-entries 選項(xiàng)和 zset-max-ziplist-value 進(jìn)行修改。
7. 五大數(shù)據(jù)類(lèi)型的應(yīng)用場(chǎng)景
對(duì)于string 數(shù)據(jù)類(lèi)型,因?yàn)閟tring 類(lèi)型是二進(jìn)制安全的,可以用來(lái)存放圖片,視頻等內(nèi)容,另外由于Redis的高性能讀寫(xiě)功能,而string類(lèi)型的value也可以是數(shù)字,可以用作計(jì)數(shù)器(INCR,DECR),比如分布式環(huán)境中統(tǒng)計(jì)系統(tǒng)的在線(xiàn)人數(shù),秒殺等。
對(duì)于 hash 數(shù)據(jù)類(lèi)型,value 存放的是鍵值對(duì),比如可以做單點(diǎn)登錄存放用戶(hù)信息。
對(duì)于 list 數(shù)據(jù)類(lèi)型,可以實(shí)現(xiàn)簡(jiǎn)單的消息隊(duì)列,另外可以利用lrange命令,做基于redis的分頁(yè)功能
對(duì)于 set 數(shù)據(jù)類(lèi)型,由于底層是字典實(shí)現(xiàn)的,查找元素特別快,另外set 數(shù)據(jù)類(lèi)型不允許重復(fù),利用這兩個(gè)特性我們可以進(jìn)行全局去重,比如在用戶(hù)注冊(cè)模塊,判斷用戶(hù)名是否注冊(cè);另外就是利用交集、并集、差集等操作,可以計(jì)算共同喜好,全部的喜好,自己獨(dú)有的喜好等功能。
對(duì)于 zset 數(shù)據(jù)類(lèi)型,有序的集合,可以做范圍查找,排行榜應(yīng)用,取 TOP N 操作等。
到此這篇關(guān)于詳解Redis數(shù)據(jù)類(lèi)型實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Redis數(shù)據(jù)類(lèi)型實(shí)現(xiàn)原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis redistemplate序列化對(duì)象配置方式
這篇文章主要介紹了redis redistemplate序列化對(duì)象配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12redis集群實(shí)現(xiàn)清理前綴相同的key
這篇文章主要介紹了redis集群實(shí)現(xiàn)清理前綴相同的key,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10redis5集群如何主動(dòng)手工切換主從節(jié)點(diǎn)命令
這篇文章主要介紹了redis5集群如何主動(dòng)手工切換主從節(jié)點(diǎn)命令,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01?Redis 串行生成順序編碼的方法實(shí)現(xiàn)
本文主要介紹了?Redis 串行生成順序編碼的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04SpringBoot集成redis錯(cuò)誤問(wèn)題及解決方法
這篇文章主要介紹了SpringBoot集成redis錯(cuò)誤問(wèn)題,本文給大家分享完美解決方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02