目錄
前言: ?
ZipList:
Ziplist的特性:
QucikList:
QuicList特征:
SkipList:
跳表特征:
RedisObijct:
?小心得:
總結(jié):
?
前言: ?
? ??? ? 在現(xiàn)代軟件開發(fā)中,數(shù)據(jù)存儲(chǔ)和處理是至關(guān)重要的一環(huán)。為了高效地管理數(shù)據(jù),并實(shí)現(xiàn)快速的讀寫操作,各種數(shù)據(jù)庫技術(shù)應(yīng)運(yùn)而生。其中,Redis作為一種高性能的內(nèi)存數(shù)據(jù)庫,廣泛應(yīng)用于緩存、會(huì)話存儲(chǔ)、消息隊(duì)列等場景。要深入了解Redis的工作原理,就必須先了解其底層數(shù)據(jù)結(jié)構(gòu)。
Redis之所以能夠在性能上表現(xiàn)出色,部分原因在于其精心設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)。這些數(shù)據(jù)結(jié)構(gòu)不僅簡單高效,而且能夠滿足各種復(fù)雜的數(shù)據(jù)處理需求。本文將深入探討Redis底層數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)原理,包括字符串、哈希、列表、集合、有序集合等,希望能夠幫助讀者更好地理解Redis的內(nèi)部機(jī)制,為進(jìn)一步應(yīng)用和優(yōu)化Redis提供指導(dǎo)。
?上一篇文章我們介紹了動(dòng)態(tài)字符串,intset,Dict這三個(gè)底層的數(shù)據(jù)結(jié)構(gòu),本文我們再來介紹一下ZipSet,QuickList,SkipList,RedisObject。
ZipList:
我們在上一篇文章最后介紹了Dict,他是基于鏈表去做的Hash表。但是鏈表有一個(gè)缺陷:內(nèi)存空間不連續(xù),因此容易產(chǎn)生內(nèi)存碎片。
而我們今天要介紹的這個(gè)數(shù)據(jù)結(jié)構(gòu),他是一種特殊的’雙端鏈表‘,由一系列特殊編碼的連續(xù)內(nèi)存塊組成,可以在任意一端進(jìn)行壓入/彈出操作,而且該操作的復(fù)雜度為(O)1
其實(shí)ZipList不是鏈表,我們只是為了方便描述才這么說。
我們可以在Redis的源碼中看一看官方作者對(duì)ZipList的數(shù)據(jù)結(jié)構(gòu)的描述:
我們用自己的圖來解釋一下各個(gè)部分的意思:
(下方此圖來自黑馬程序員,如有侵權(quán),立即刪除)?
?Ziplist的優(yōu)勢就在于:他的entry的字節(jié)大小是不固定的。
而雖然我們這么做可以減少內(nèi)存的浪費(fèi),但是他存在一個(gè)問題:當(dāng)節(jié)點(diǎn)的長度不固定的時(shí)候,我們是如何遍歷節(jié)點(diǎn)的?畢竟已經(jīng)無法通過索引下標(biāo)進(jìn)行遍歷了。要想知道答案,就得先從他的結(jié)點(diǎn)結(jié)構(gòu)看起:
結(jié)點(diǎn)的結(jié)構(gòu)數(shù)據(jù)如圖所示:
Pervious:前一個(gè)結(jié)點(diǎn)的長度,占一個(gè)或者5個(gè)字節(jié)。
? ? ? ? 如果前一個(gè)結(jié)點(diǎn)的長度小于254,則采用一個(gè)字節(jié)來保存這個(gè)長度值。
? ? ? ? 如果前一個(gè)結(jié)點(diǎn)的長度大于254,則采用五個(gè)字節(jié)來保存這個(gè)長度值,第一個(gè)字節(jié)為0xfe,后? ????????面四個(gè)才是真實(shí)的長度數(shù)據(jù)?
encoding:編碼屬性,記錄content的數(shù)據(jù)類型(字符串還是整數(shù))以及長度,占用1個(gè)2個(gè)或5個(gè)字節(jié)。
contents: 負(fù)責(zé)保存結(jié)點(diǎn)的數(shù)據(jù),可以是字符串或整數(shù)
通過了解Ziplist的結(jié)點(diǎn)結(jié)構(gòu)之后,其實(shí)我們就可以知道:如果我們想要知道下一個(gè)結(jié)點(diǎn)的長度,只需要用起始地址+當(dāng)前結(jié)點(diǎn)三部分的字節(jié)數(shù) ,那就是下一個(gè)結(jié)點(diǎn)的位置了。
如果我們想要找到上一個(gè)結(jié)點(diǎn)的位置,也只需要拿當(dāng)前結(jié)點(diǎn)的起始位置-上一個(gè)結(jié)點(diǎn)的長度就好了。
?
而ZipList會(huì)有連鎖更新的問題,很簡單:每一個(gè)結(jié)點(diǎn)都存儲(chǔ)著上一個(gè)結(jié)點(diǎn)的信息,如果當(dāng)前結(jié)點(diǎn)的長度發(fā)生變化,那么下一個(gè)結(jié)點(diǎn)中存儲(chǔ)上一個(gè)結(jié)點(diǎn)長度的字節(jié)數(shù)也能會(huì)發(fā)生變化,比如以前長度是5,一個(gè)字節(jié)存儲(chǔ),現(xiàn)在變成了255,五個(gè)字節(jié)存儲(chǔ)。如果每一個(gè)結(jié)點(diǎn)都會(huì)發(fā)生類似的情況,就會(huì)引發(fā)連鎖更新問題
但是目前Redis的作者沒有解決這個(gè)問題,因?yàn)樗母怕屎艿汀?/strong>
其實(shí)我覺得解決這個(gè)問題的思路很簡單,如果我們的節(jié)點(diǎn)有100萬條數(shù)據(jù),那么我們?nèi)绻|發(fā)連鎖更新的話,就會(huì)導(dǎo)致線程阻塞。那我覺得解決思路也很簡單,可以和Dict的Rehash的處理方案一樣,用兩張表,一張進(jìn)行正常的讀取,不阻塞線程,另外一張表進(jìn)行漸進(jìn)式的連鎖更新。
Ziplist的特性:
1.壓縮列表可以看作是一種連續(xù)空間的”雙向鏈表“。
2.節(jié)點(diǎn)之間不是通過指針相連接,而是記錄上一個(gè)節(jié)點(diǎn)和本節(jié)點(diǎn)的長度來尋址,內(nèi)存占用低。
3.如果列表數(shù)據(jù)過多,導(dǎo)致鏈表長度過長,會(huì)影響查詢性能。
4.增加或刪除較大數(shù)據(jù)的時(shí)候可能會(huì)引發(fā)連續(xù)更新問題。
QucikList:
ZipList雖然節(jié)省空間,但是他必須是連續(xù)的空間,如果占用內(nèi)存比較多的話,申請效率就會(huì)很低。
為了解決這個(gè)問題,我們想到了ZipList分片,創(chuàng)建多個(gè)ZipList來存儲(chǔ)數(shù)據(jù):
而如何把這多個(gè)ZipList建立聯(lián)系呢?
可以把這幾個(gè)ZipList用指針連接起來。
而QuickList就是這種結(jié)構(gòu),他是一個(gè)雙端鏈表,不過每一個(gè)節(jié)點(diǎn)是ZipList。
為了避免QuickList中的每一個(gè) ZipList中entry過多,Redis提供了一個(gè)配置項(xiàng):list-max-ziplist-size來限制。
如果值為正,則標(biāo)識(shí)ZipList中允許的最大entry個(gè)數(shù)的最大值。
如果值為負(fù),則代表ZipList的最大內(nèi)存大小,分五種情況:
- -1:每個(gè)Zip的內(nèi)存大小不能超過4kb
- -2:每個(gè)Zip的內(nèi)存大小不能超過8kb
- -3:每個(gè)Zip的內(nèi)存大小不能超過16kb
- -4:每個(gè)Zip的內(nèi)存大小不能超過32kb
- -5:每個(gè)Zip的內(nèi)存大小不能超過64kb
這個(gè)參數(shù)的默認(rèn)值為-2。?
除了對(duì)每一個(gè)ZipList限制大小之外,QuicList還可以對(duì)節(jié)點(diǎn)的ZipList做壓縮?,通過配置項(xiàng)list-compress-depth來控制,因?yàn)殒湵硪话愣际菑氖孜苍L問比較多,所以首位是不壓縮的。而這個(gè)配置的參數(shù)就是控制首尾不壓縮的節(jié)點(diǎn)個(gè)數(shù)。
常見參數(shù):
- 0:所有節(jié)點(diǎn)都不壓縮
- 1:標(biāo)識(shí)首尾各有一個(gè)結(jié)點(diǎn)不壓縮,中間結(jié)點(diǎn)壓縮
- 2:標(biāo)識(shí)QuickList的首尾各有兩個(gè)結(jié)點(diǎn)不壓縮,中間結(jié)點(diǎn)壓縮。
講了這么多,我們來看一看Redis中的源碼:
quicklist源碼:
quicklistNode源碼:
?用黑馬的圖來看一看ZipList的數(shù)據(jù)結(jié)構(gòu):
QuicList特征:
1.是一個(gè)結(jié)點(diǎn)為ZipList的雙端鏈表。
2.結(jié)點(diǎn)采用了ZipList,解決了傳統(tǒng)鏈表的內(nèi)存占用問題。
3.控制了ZipList的大小,解決了連續(xù)內(nèi)存空間的申請效率?。
4.中間節(jié)點(diǎn)可以壓縮,進(jìn)一步節(jié)省了內(nèi)存。
SkipList:
?雖然前面的幾個(gè)List在空間方面做了優(yōu)化,但是他的查詢效率卻很低,都需要遍歷查詢。
而SkipList(跳表)就是為了提高查詢效率而創(chuàng)建的一種數(shù)據(jù)結(jié)構(gòu)。
SkipList首先是鏈表,但是他和傳統(tǒng)的鏈表比起來,有以下區(qū)別:
1.元素按照升序排列。
2.允許一個(gè)結(jié)點(diǎn)包含多個(gè)指針,指針跨度不同。
其實(shí)這個(gè)很好理解,既然是鏈表,那么我們要尋找一個(gè)元素就必須遍歷鏈表。這是因?yàn)殒湵淼拿恳粋€(gè)結(jié)點(diǎn)都存儲(chǔ)的下一個(gè)結(jié)點(diǎn)的位置,沒有跨越的指針。
而我們?nèi)绻梢宰屢粋€(gè)結(jié)點(diǎn)的指針指向的距離不再是1,那不就在一定程度上優(yōu)化了搜索效率。
傳統(tǒng)鏈表:
跳表思想:
??
這樣的話,傳統(tǒng)鏈表我們?nèi)绻獙ふ?,就要遍歷4個(gè)結(jié)點(diǎn),但是現(xiàn)在基于跳表這種思想我們就只用遍歷三個(gè)結(jié)點(diǎn)。
而這也就是跳表需要元素有序排列的意義,如果元素是亂序排放的,那么他往哪里跳就毫無頭緒。
?接下來我們看一看Redis中的跳表的數(shù)據(jù)結(jié)構(gòu):
我們來看一下一個(gè)成熟的跳表結(jié)構(gòu):
?
跳表特征:
1.跳表是一個(gè)雙向鏈表。
2.跳表每一個(gè)接單都可以包含多層指針,層數(shù)在1-32之間。
3.不同層指針到下一個(gè)結(jié)點(diǎn)的跨度不同,層級(jí)越高,跨度越大。
4.增刪改查效率和紅黑樹一樣,但是實(shí)現(xiàn)更加簡單。
RedisObijct:
Redis中任意數(shù)據(jù)類型的鍵和值都會(huì)被封裝成為一個(gè)RedisObject,也叫做Redis對(duì)象,一個(gè)鍵對(duì)象,一個(gè)值對(duì)象。
typedef struct redisObject{
//對(duì)象類型,分別是string,set,zet,list,hash
unsigned type:4;
//編碼方式
unsigned encoding:4;
//當(dāng)前對(duì)象最后一次被訪問的時(shí)間
unsighed lru:LRU_BITS;
//引用計(jì)數(shù)器,如果為0說明當(dāng)前對(duì)象沒有被引用,可以被回收
int refcount;
//指向五種數(shù)據(jù)類型對(duì)應(yīng)的內(nèi)存空間
void * ptr;
} robj;
?小心得:
一個(gè)redis對(duì)象的頭部,不包含引用對(duì)象的指針就已經(jīng)占據(jù)了16個(gè)字節(jié)的大小。所以我們在實(shí)際使用redis的時(shí)候,盡量避免直接使用String 這種數(shù)據(jù)結(jié)構(gòu),因?yàn)樗敲恳粋€(gè)字符串都需要一個(gè)鍵,那么就會(huì)多占用對(duì)象頭,會(huì)造成很大的性能浪費(fèi)。
?
這張圖就很好的表述了我的意思。
RedisObject常見的編碼有:?
總結(jié):
?本文介紹完了Redis的底層數(shù)據(jù)機(jī)構(gòu),從下一篇來時(shí),我們就要開始學(xué)習(xí)Redis我們表層使用的五種數(shù)據(jù)結(jié)構(gòu):String ,set,zet,hash,list。
如果我的內(nèi)容對(duì)你有幫助,請點(diǎn)贊,評(píng)論,收藏。創(chuàng)作不易,大家的支持就是我堅(jiān)持下去的動(dòng)力!文章來源:http://www.zghlxwxcb.cn/news/detail-848360.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-848360.html
到了這里,關(guān)于【從零開始學(xué)習(xí)Redis | 第八篇】認(rèn)識(shí)Redis底層數(shù)據(jù)結(jié)構(gòu)(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!