Redis都做了哪些加快速度的設(shè)計(jì)
列表對(duì)象是 Redis
中 5
種基礎(chǔ)數(shù)據(jù)類型之一,在 Redis 3.2
版本之前,列表對(duì)象底層存儲(chǔ)結(jié)構(gòu)有兩種:linkedlist
(雙端列表)和 ziplist
(壓縮列表),而在 Redis 3.2
版本之后,列表對(duì)象底層存儲(chǔ)結(jié)構(gòu)只有一種:quicklist
(快速列表),難道通過精心設(shè)計(jì)的 ziplist
最終被 Redis
拋棄了嗎?
列表對(duì)象
同字符串對(duì)象一樣,列表對(duì)象到底使用哪一種數(shù)據(jù)結(jié)構(gòu)來進(jìn)行存儲(chǔ)也是通過編碼來進(jìn)行區(qū)分:
編碼屬性 | 描述 | object encoding命令返回值 |
---|---|---|
OBJ_ENCODING_LINKEDLIST | 使用 linkedlist 實(shí)現(xiàn)列表對(duì)象 |
linkedlist |
OBJ_ENCODING_ZIPLIST | 使用 ziplist 實(shí)現(xiàn)列表對(duì)象 |
ziplist |
OBJ_ENCODING_QUICKLIST | 使用 quicklist 實(shí)現(xiàn)列表對(duì)象 |
quicklist |
linkedlist
linkedlist
是一個(gè)雙向列表,每個(gè)節(jié)點(diǎn)都會(huì)存儲(chǔ)指向上一個(gè)節(jié)點(diǎn)和指向下一個(gè)節(jié)點(diǎn)的指針。linkedlist
因?yàn)槊總€(gè)節(jié)點(diǎn)之間的空間是不連續(xù)的,所以可能會(huì)造成過多的內(nèi)存空間碎片。
linkedlist存儲(chǔ)結(jié)構(gòu)
鏈表中每一個(gè)節(jié)點(diǎn)都是一個(gè) listNode
對(duì)象(源碼 adlist.h
內(nèi)),不過需要注意的是,列表中的 value
其實(shí)也是一個(gè)字符串對(duì)象,其他幾種數(shù)據(jù)類型其內(nèi)部最終也是會(huì)嵌套字符串對(duì)象,字符串對(duì)象也是唯一一種會(huì)被其他對(duì)象引用的基本類型:
typedef struct listNode { struct listNode *prev;//前一個(gè)節(jié)點(diǎn) struct listNode *next;//后一個(gè)節(jié)點(diǎn) void *value;//值(字符串對(duì)象) } listNode;
然后會(huì)將其再進(jìn)行封裝成為一個(gè) list
對(duì)象(源碼 adlist.h
內(nèi)):
typedef struct list { listNode *head;//頭節(jié)點(diǎn) listNode *tail;//尾節(jié)點(diǎn) void *(*dup)(void *ptr);//節(jié)點(diǎn)值復(fù)制函數(shù) void (*free)(void *ptr);//節(jié)點(diǎn)值釋放函數(shù) int (*match)(void *ptr, void *key);//節(jié)點(diǎn)值對(duì)比函數(shù) unsigned long len;//節(jié)點(diǎn)數(shù)量 } list;
Redis
中對(duì) linkedlist
的訪問是以 NULL
值為終點(diǎn)的,因?yàn)?head
節(jié)點(diǎn)的 prev
節(jié)點(diǎn)為 NULL
,tail
節(jié)點(diǎn)的 next
節(jié)點(diǎn)也為 NULL
,所以從頭節(jié)點(diǎn)開始遍歷,當(dāng)發(fā)現(xiàn) tail
為 NULL
時(shí),則可以認(rèn)為已經(jīng)到了列表末尾。
當(dāng)我們?cè)O(shè)置一個(gè)列表對(duì)象時(shí),在 Redis 3.2
版本之前我們可以得到如下存儲(chǔ)示意圖:
ziplist
壓縮列表在前面已經(jīng)介紹過,想要詳細(xì)了解的可以點(diǎn)擊這里。
linkedlist 和 ziplist 的選擇
在 Redis3.2
之前,linkedlist
和 ziplist
兩種編碼可以進(jìn)選擇切換,如果需要列表使用 ziplist
編碼進(jìn)行存儲(chǔ),則必須滿足以下兩個(gè)條件:
列表對(duì)象保存的所有字符串元素的長度都小于 64
字節(jié)。列表對(duì)象保存的元素?cái)?shù)量小于 512
個(gè)。
一旦不滿足這兩個(gè)條件的任意一個(gè),則會(huì)使用 linkedlist
編碼進(jìn)行存儲(chǔ)。
PS:這兩個(gè)條件可以通過參數(shù) list-max-ziplist-value
和 list-max-ziplist-entries
進(jìn)行修改。
這兩種列表能在特定的場景下發(fā)揮各自的作用,應(yīng)該來說已經(jīng)能滿足大部分需求了,然后 Redis
并不滿足于此,于是一場改革引發(fā)了,quicklist
橫空出世。
quicklist
在 Redis 3.2
版本之后,為了進(jìn)一步提升 Redis
的性能,列表對(duì)象統(tǒng)一采用 quicklist
來存儲(chǔ)列表對(duì)象。quicklist
存儲(chǔ)了一個(gè)雙向列表,每個(gè)列表的節(jié)點(diǎn)是一個(gè) ziplist
,所以實(shí)際上 quicklist
并不是一個(gè)新的數(shù)據(jù)結(jié)構(gòu),它就是linkedlist
和 ziplist
的結(jié)合,然后被命名為快速列表。
quicklist 內(nèi)部存儲(chǔ)結(jié)構(gòu)
quicklist
中每一個(gè)節(jié)點(diǎn)都是一個(gè) quicklistNode
對(duì)象,其數(shù)據(jù)結(jié)構(gòu)定義如下:
typedef struct quicklistNode { struct quicklistNode *prev;//前一個(gè)節(jié)點(diǎn) struct quicklistNode *next;//后一個(gè)節(jié)點(diǎn) unsigned char *zl;//當(dāng)前指向的ziplist或者quicklistLZF unsigned int sz;//當(dāng)前ziplist占用字節(jié) unsigned int count : 16;//ziplist中存儲(chǔ)的元素個(gè)數(shù),16字節(jié)(最大65535個(gè)) unsigned int encoding : 2; //是否采用了LZF壓縮算法壓縮節(jié)點(diǎn) 1:RAW 2:LZF unsigned int container : 2; //存儲(chǔ)結(jié)構(gòu),NONE=1, ZIPLIST=2 unsigned int recompress : 1; //當(dāng)前ziplist是否需要再次壓縮(如果前面被解壓過則為true,表示需要再次被壓縮) unsigned int attempted_compress : 1;//測試用 unsigned int extra : 10; //后期留用 } quicklistNode;
然后各個(gè) quicklistNode
就構(gòu)成了一個(gè)快速列表 quicklist
:
typedef struct quicklist { quicklistNode *head;//列表頭節(jié)點(diǎn) quicklistNode *tail;//列表尾節(jié)點(diǎn) unsigned long count;//ziplist中一共存儲(chǔ)了多少元素,即:每一個(gè)quicklistNode內(nèi)的count相加 unsigned long len; //雙向鏈表的長度,即quicklistNode的數(shù)量 int fill : 16;//填充因子 unsigned int compress : 16;//壓縮深度 0-不壓縮 } quicklist;
根據(jù)這兩個(gè)結(jié)構(gòu),我們可以得到 Redis 3.2
版本之后的列表對(duì)象的一個(gè)存儲(chǔ)結(jié)構(gòu)示意圖:
quicklist 的 compress 屬性
compress
是用來表示壓縮深度,ziplist
除了內(nèi)存空間是連續(xù)之外,還可以采用特定的 LZF
壓縮算法來將節(jié)點(diǎn)進(jìn)行壓縮存儲(chǔ),從而更進(jìn)一步的節(jié)省空間,壓縮深度可以通過參數(shù) list-compress-depth
控制:
0:不壓縮(默認(rèn)值)
1:首尾第1個(gè)元素不壓縮
2:首位前2個(gè)元素不壓縮
3:首尾前3個(gè)元素不壓縮以此類推
注意:之所以采取這種壓縮兩端節(jié)點(diǎn)的方式是因?yàn)楹芏鄨鼍岸际莾啥说脑卦L問率最高的,而中間元素訪問率相對(duì)較低,所以在實(shí)際使用時(shí),我們可以根據(jù)自己的實(shí)際情況選擇是否進(jìn)行壓縮,以及具體的壓縮深度。
quicklistNode 的 zl 指針
zl
指針默認(rèn)指向了 ziplist
,上面提到 quicklistNode
中有一個(gè) sz
屬性記錄了當(dāng)前 ziplist
占用的字節(jié),不過這僅僅限于當(dāng)前節(jié)點(diǎn)沒有被壓縮(通過LZF
壓縮算法)的情況,如果當(dāng)前節(jié)點(diǎn)被壓縮了,那么被壓縮節(jié)點(diǎn)的 zl
指針會(huì)指向另一個(gè)對(duì)象 quicklistLZF
,而不會(huì)直接指向 ziplist
。quicklistLZF
是一個(gè) 4+N
字節(jié)的結(jié)構(gòu):
typedef struct quicklistLZF { unsigned int sz;// LZF大小,占用4字節(jié) char compressed[];//被壓縮的內(nèi)容,占用N字節(jié) } quicklistLZF;
quicklist 對(duì)比原始兩種編碼的改進(jìn)
quicklist
同樣采用了 linkedlist
的雙端列表特性,然后 quicklist
中的每個(gè)節(jié)點(diǎn)又是一個(gè) ziplist
,所以quicklist
就是綜合平衡考慮了 linkedlist
容易產(chǎn)生空間碎片的問題和 ziplist
的讀寫性能兩個(gè)維度而設(shè)計(jì)出來的一種數(shù)據(jù)結(jié)構(gòu)。使用 quicklist
需要注意以下 2
點(diǎn):
如果 ziplist
中的 entry
個(gè)數(shù)過少,最極端情況就是只有 1
個(gè) entry
的壓縮列表,那么此時(shí) quicklist
就相當(dāng)于退化成了一個(gè)普通的 linkedlist
。如果 ziplist
中的 entry
過多,那么也會(huì)導(dǎo)致一次性需要申請(qǐng)的內(nèi)存空間過大(ziplist
空間是連續(xù)的),而且因?yàn)?ziplist
本身的就是以時(shí)間換空間,所以會(huì)過多 entry
也會(huì)影響到列表對(duì)象的讀寫性能。
ziplist
中的 entry
個(gè)數(shù)可以通過參數(shù) list-max-ziplist-size
來控制:
list-max-ziplist-size 1
注意:這個(gè)參數(shù)可以配置正數(shù)也可以配置負(fù)數(shù)。正數(shù)表示限制每個(gè)節(jié)點(diǎn)中的 entry
數(shù)量,如果是負(fù)數(shù)則只能為 -1~-5
,其代表的含義如下:
-1:每個(gè) ziplist
最多只能為 4KB
-2:每個(gè) ziplist
最多只能為 8KB
-3:每個(gè) ziplist
最多只能為 16KB
-4:每個(gè) ziplist
最多只能為 32KB
-5:每個(gè) ziplist
最多只能為 64KB
列表對(duì)象常用操作命令
lpush key value1 value2:將一個(gè)或者多個(gè) value
插入到列表 key
的頭部,key
不存在則創(chuàng)建 key
(value2
在value1
之后)。
- lpushx key value1 value2:將一個(gè)或者多個(gè)
value
插入到列表key
的頭部,key
不存在則不做任何處理(value2
在value1
之后)。 - lpop key:移除并返回
key
值的列表頭元素。 - rpush key value1 value2:將一個(gè)或者多個(gè)
value
插入到列表key
的尾部,key
不存在則創(chuàng)建key
(value2
在value1
之后)。 - rpushx key value1 vaue2:將一個(gè)或者多個(gè)
value
插入到列表key
的尾部,key
不存在則不做任何處理(value2
在value1
之后)。 - rpop key:移除并返回列表
key
的尾元素。 - llen key:返回列表
key
的長度。 - lindex key index:返回列表
key
中下標(biāo)為index
的元素。index
為正數(shù)(從0
開始)表示從隊(duì)頭開始算,index
為負(fù)數(shù)(從-1開始)則表示從隊(duì)尾開始算。 - lrange key start stop:返回列表
key
中下標(biāo)[start,end]
之間的元素。 - lset key index value:將
value
設(shè)置到列表key
中指定index
位置,key
不存在或者index
超出范圍則會(huì)報(bào)錯(cuò)。 ltrim key start end:截取列表中[start,end]
之間的元素,并替換原列表保存。
了解了操作列表對(duì)象的常用命令,我們就可以來驗(yàn)證下前面提到的列表對(duì)象的類型和編碼了,在測試之前為了防止其他 key
值的干擾,我們先執(zhí)行 flushall
命令清空 Redis
數(shù)據(jù)庫。
接下來依次輸入命令:
lpush name zhangsan
type name
object encoding name
可以看到,通過 type
命令輸出的是 list
,說明當(dāng)前 name
存的是一個(gè)列表對(duì)象,并且編碼是 quicklist
(示例中用的是 5.0.5
版本)。
總結(jié)
本文主要介紹了 Redis
中 5
種常用數(shù)據(jù)類型中的 列表對(duì)象,并介紹了底層的存儲(chǔ)結(jié)構(gòu) quicklist
,并分別對(duì)舊版本的兩種底層數(shù)據(jù) linkedlist
和 ziplist
進(jìn)行了分析對(duì)比得出了為什么 Redis
最終要采用 quicklist
來存儲(chǔ)列表對(duì)象。
到此這篇關(guān)于Redis都做了哪些加快速度的設(shè)計(jì)的文章就介紹到這了,更多相關(guān)Redis 加快速度的設(shè)計(jì)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis分布式鎖如何設(shè)置超時(shí)時(shí)間
這篇文章主要介紹了Redis分布式鎖如何設(shè)置超時(shí)時(shí)間,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11SpringBoot整合Mybatis-plus和Redis實(shí)現(xiàn)投票功能
投票功能是一個(gè)非常常見的Web應(yīng)用場景,這篇文章將為大家介紹一下如何將Redis和Mybatis-plus整合到SpringBoot中,實(shí)現(xiàn)投票功能,感興趣的可以了解一下2023-05-05Redis主從復(fù)制問題和擴(kuò)容問題的解決思路
這篇文章主要介紹了Redis主從復(fù)制問題和擴(kuò)容問題的解決思路,其中擴(kuò)容問題的解決思路來自Redis作者,需要的朋友可以參考下2014-06-06redis 存儲(chǔ)對(duì)象的方法對(duì)比分析
這篇文章主要介紹了redis 存儲(chǔ)對(duì)象的方法對(duì)比分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07詳解Redis高效恢復(fù)策略內(nèi)存快照與AOF
這篇文章主要為大家介紹了Redis高效恢復(fù)策略內(nèi)存快照與AOF及對(duì)比詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12詳解Redis緩存預(yù)熱的實(shí)現(xiàn)方法
緩存預(yù)熱是一種在程序啟動(dòng)或緩存失效之后,主動(dòng)將熱點(diǎn)數(shù)據(jù)加載到緩存中的策略,本文將給大家分享一下如何實(shí)現(xiàn)Redis的緩存預(yù)熱,文中有詳細(xì)的實(shí)現(xiàn)代碼,需要的朋友可以參考下2023-10-10Redis設(shè)置鍵的生存時(shí)間或過期時(shí)間的方法詳解
這篇文章主要介紹了Redis如何設(shè)置鍵的生存時(shí)間或過期時(shí)間,通過EXPIRE命令或者PEXIPIRE命令,客戶端可以以秒或者毫秒精度為數(shù)據(jù)庫中的某個(gè)鍵設(shè)置生存時(shí)間,文中有詳細(xì)的代碼供供大家參考,需要的朋友可以參考下2024-03-03