列表對象是 Redis
中 5
種基礎數據類型之一,在 Redis 3.2
版本以前,列表對象底層存儲結構有兩種:linkedlist
(雙端列表)和 ziplist
(壓縮列表),而在 Redis 3.2
版本以後,列表對象底層存儲結構只有一種:quicklist
(快速列表),難道經過精心設計的 ziplist
最終被 Redis
拋棄了嗎?html
同字符串對象同樣,列表對象到底使用哪種數據結構來進行存儲也是經過編碼來進行區分:java
編碼屬性 | 描述 | object encoding命令返回值 |
---|---|---|
OBJ_ENCODING_LINKEDLIST | 使用 linkedlist 實現列表對象 |
linkedlist |
OBJ_ENCODING_ZIPLIST | 使用 ziplist 實現列表對象 |
ziplist |
OBJ_ENCODING_QUICKLIST | 使用 quicklist 實現列表對象 |
quicklist |
linkedlist
是一個雙向列表,每一個節點都會存儲指向上一個節點和指向下一個節點的指針。linkedlist
由於每一個節點之間的空間是不連續的,因此可能會形成過多的內存空間碎片。node
鏈表中每個節點都是一個 listNode
對象(源碼 adlist.h
內),不過須要注意的是,列表中的 value
其實也是一個字符串對象,其餘幾種數據類型其內部最終也是會嵌套字符串對象,字符串對象也是惟一一種會被其餘對象引用的基本類型:算法
typedef struct listNode { struct listNode *prev;//前一個節點 struct listNode *next;//後一個節點 void *value;//值(字符串對象) } listNode;
而後會將其再進行封裝成爲一個 list
對象(源碼 adlist.h
內):數據庫
typedef struct list { listNode *head;//頭節點 listNode *tail;//尾節點 void *(*dup)(void *ptr);//節點值複製函數 void (*free)(void *ptr);//節點值釋放函數 int (*match)(void *ptr, void *key);//節點值對比函數 unsigned long len;//節點數量 } list;
Redis
中對 linkedlist
的訪問是以 NULL
值爲終點的,由於 head
節點的 prev
節點爲 NULL
,tail
節點的 next
節點也爲 NULL
,因此從頭節點開始遍歷,當發現 tail
爲 NULL
時,則能夠認爲已經到了列表末尾。數據結構
當咱們設置一個列表對象時,在 Redis 3.2
版本以前咱們能夠獲得以下存儲示意圖:函數
壓縮列表在前面已經介紹過,想要詳細瞭解的能夠點擊這裏。性能
在 Redis3.2
以前,linkedlist
和 ziplist
兩種編碼能夠進選擇切換,若是須要列表使用 ziplist
編碼進行存儲,則必須知足如下兩個條件:測試
64
字節。512
個。一旦不知足這兩個條件的任意一個,則會使用 linkedlist
編碼進行存儲。ui
PS:這兩個條件能夠經過參數 list-max-ziplist-value
和 list-max-ziplist-entries
進行修改。
這兩種列表能在特定的場景下發揮各自的做用,應該來講已經能知足大部分需求了,而後 Redis
並不知足於此,因而一場改革引起了,quicklist
橫空出世。
在 Redis 3.2
版本以後,爲了進一步提高 Redis
的性能,列表對象統一採用 quicklist
來存儲列表對象。quicklist
存儲了一個雙向列表,每一個列表的節點是一個 ziplist
,因此實際上 quicklist
並非一個新的數據結構,它就是linkedlist
和 ziplist
的結合,而後被命名爲快速列表。
quicklist
中每個節點都是一個 quicklistNode
對象,其數據結構定義以下:
typedef struct quicklistNode { struct quicklistNode *prev;//前一個節點 struct quicklistNode *next;//後一個節點 unsigned char *zl;//當前指向的ziplist或者quicklistLZF unsigned int sz;//當前ziplist佔用字節 unsigned int count : 16;//ziplist中存儲的元素個數,16字節(最大65535個) unsigned int encoding : 2; //是否採用了LZF壓縮算法壓縮節點 1:RAW 2:LZF unsigned int container : 2; //存儲結構,NONE=1, ZIPLIST=2 unsigned int recompress : 1; //當前ziplist是否須要再次壓縮(若是前面被解壓過則爲true,表示須要再次被壓縮) unsigned int attempted_compress : 1;//測試用 unsigned int extra : 10; //後期留用 } quicklistNode;
而後各個 quicklistNode
就構成了一個快速列表 quicklist
:
typedef struct quicklist { quicklistNode *head;//列表頭節點 quicklistNode *tail;//列表尾節點 unsigned long count;//ziplist中一共存儲了多少元素,即:每個quicklistNode內的count相加 unsigned long len; //雙向鏈表的長度,即quicklistNode的數量 int fill : 16;//填充因子 unsigned int compress : 16;//壓縮深度 0-不壓縮 } quicklist;
根據這兩個結構,咱們能夠獲得 Redis 3.2
版本以後的列表對象的一個存儲結構示意圖:
compress
是用來表示壓縮深度,ziplist
除了內存空間是連續以外,還能夠採用特定的 LZF
壓縮算法來將節點進行壓縮存儲,從而更進一步的節省空間,壓縮深度能夠經過參數 list-compress-depth
控制:
注意:之因此採起這種壓縮兩端節點的方式是由於不少場景都是兩端的元素訪問率最高的,而中間元素訪問率相對較低,因此在實際使用時,咱們能夠根據本身的實際狀況選擇是否進行壓縮,以及具體的壓縮深度。
zl
指針默認指向了 ziplist
,上面提到 quicklistNode
中有一個 sz
屬性記錄了當前 ziplist
佔用的字節,不過這僅僅限於當前節點沒有被壓縮(經過LZF
壓縮算法)的狀況,若是當前節點被壓縮了,那麼被壓縮節點的 zl
指針會指向另外一個對象 quicklistLZF
,而不會直接指向 ziplist
。quicklistLZF
是一個 4+N
字節的結構:
typedef struct quicklistLZF { unsigned int sz;// LZF大小,佔用4字節 char compressed[];//被壓縮的內容,佔用N字節 } quicklistLZF;
quicklist
一樣採用了 linkedlist
的雙端列表特性,而後 quicklist
中的每一個節點又是一個 ziplist
,因此quicklist
就是綜合平衡考慮了 linkedlist
容易產生空間碎片的問題和 ziplist
的讀寫性能兩個維度而設計出來的一種數據結構。使用 quicklist
須要注意如下 2
點:
ziplist
中的 entry
個數過少,最極端狀況就是隻有 1
個 entry
的壓縮列表,那麼此時 quicklist
就至關於退化成了一個普通的 linkedlist
。ziplist
中的 entry
過多,那麼也會致使一次性須要申請的內存空間過大(ziplist
空間是連續的),並且由於 ziplist
自己的就是以時間換空間,因此會過多 entry
也會影響到列表對象的讀寫性能。ziplist
中的 entry
個數能夠經過參數 list-max-ziplist-size
來控制:
list-max-ziplist-size 1
注意:這個參數能夠配置正數也能夠配置負數。正數表示限制每一個節點中的 entry
數量,若是是負數則只能爲 -1~-5
,其表明的含義以下:
ziplist
最多隻能爲 4KB
ziplist
最多隻能爲 8KB
ziplist
最多隻能爲 16KB
ziplist
最多隻能爲 32KB
ziplist
最多隻能爲 64KB
value
插入到列表 key
的頭部,key
不存在則建立 key
(value2
在value1
以後)。value
插入到列表 key
的頭部,key
不存在則不作任何處理(value2
在value1
以後)。key
值的列表頭元素。value
插入到列表 key
的尾部,key
不存在則建立 key
(value2
在value1
以後)。value
插入到列表 key
的尾部,key
不存在則不作任何處理(value2
在value1
以後)。key
的尾元素。key
的長度。key
中下標爲 index
的元素。index
爲正數(從 0
開始)表示從隊頭開始算,index
爲負數(從-1開始)則表示從隊尾開始算。key
中下標 [start,end]
之間的元素。value
設置到列表 key
中指定 index
位置,key
不存在或者 index
超出範圍則會報錯。[start,end]
之間的元素,並替換原列表保存。瞭解了操做列表對象的經常使用命令,咱們就能夠來驗證下前面提到的列表對象的類型和編碼了,在測試以前爲了防止其餘 key
值的干擾,咱們先執行 flushall
命令清空 Redis
數據庫。
接下來依次輸入命令:
lpush name zhangsan
type name
object encoding name
能夠看到,經過 type
命令輸出的是 list
,說明當前 name
存的是一個列表對象,而且編碼是 quicklist
(示例中用的是 5.0.5
版本)。
本文主要介紹了 Redis
中 5
種經常使用數據類型中的 列表對象,並介紹了底層的存儲結構 quicklist
,並分別對舊版本的兩種底層數據 linkedlist
和 ziplist
進行了分析對比得出了爲何 Redis
最終要採用 quicklist
來存儲列表對象。