注意:本系列文章分析的 Redis 源碼版本:github.com/Sidfate/red…,是文章發佈時間的最新版。node
在介紹快速列表以前,建議你要先了解下 ziplist 和 adlist,特別是 ziplist (參考個人文章《【最完整系列】Redis-結構篇-壓縮列表》),關於 adlist 下面我會簡單解釋一下。git
adlist 其實就是一個常規的雙向鏈表實現,show you source code:github
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
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
ok,在咱們切入正題前,先講下在早期的 redis 版本中,列表鍵的實現分 2 種,元素數量少時用 ziplist,多時用 adlist,但在 3.2(網上資料查到的,不必定準確)以後的版本里,都用 quicklist 取代:算法
> lpush test_list 1
(integer) 1
> object encoding test_list
"quicklist"
複製代碼
爲何要專門設計一個 quicklist 來從新定義呢?接下來從源碼的角度來解讀。首先仍是看註釋(redis 源碼的註釋真香):shell
quicklist.c - A doubly linked list of ziplists數據結構
而後再看下源碼結構實現:post
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *zl;
unsigned int sz; /* ziplist 佔用的字節總數 */
unsigned int count : 16; /* ziplist 的元素個數 */
unsigned int encoding : 2; /* 是否被壓縮,2表示被壓縮,1表示原生 */
unsigned int container : 2;
unsigned int recompress : 1;
unsigned int attempted_compress : 1;
unsigned int extra : 10;
} quicklistNode;
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* 全部 ziplists 的元素總和 */
unsigned long len; /* quicklistNodes 的個數 */
int fill : 16;
unsigned int compress : 16;
} quicklist;
複製代碼
字段的詳細解釋請參照本文末尾的字段詳解部分。性能
所你們明白了嗎,爲何我在一開始讓你們先去了解下 ziplist 和 adlist,由於 quicklist 其實就是一個以 ziplist 爲節點(quicklistNode 中存放指向 ziplist 的指針)的 adlist,沒圖說個JB:單元測試
知道結構後再回到咱們最初的問題,quicklist 的結構爲何這樣設計呢?quicklist 平衡了 ziplist 和 adlist 的優缺點:
可是問題仍是類了,quicklist 中 quicklistNode 包含多長的 ziplist 多少合適呢?長度若是小了,跟普通的雙向鏈表也就差很少了,仍是有內存碎片的問題;長度大了,每一個 quicklist 節點上的 ziplist 須要大片的連續內存,操做內存的效率仍是降低了。因此這個長度確定是一個平衡值,它是 redis 提供的一個選項配置,默認是 -2,來看下官方說明:
# -5: max size: 64 Kb <-- not recommended for normal workloads
# -4: max size: 32 Kb <-- not recommended
# -3: max size: 16 Kb <-- probably not recommended
# -2: max size: 8 Kb <-- good
# -1: max size: 4 Kb <-- good
# Positive numbers mean store up to _exactly_ that number of elements
# per list node.
# The highest performing option is usually -2 (8 Kb size) or -1 (4 Kb size),
# but if your use case is unique, adjust the settings as necessary.
list-max-ziplist-size -2
複製代碼
這裏我就不翻譯了,原生英文解釋的很清楚了,惟一一點要說明下的是,當 list-max-ziplist-size 設置爲正數時,表示每一個 list 節點中儲存元素個數。
你們仔細看 quicklist 的源碼結構時,可能還注意到出現了不少 compress 的字樣,這是由於 redis 爲 quicklist 提供了一套壓縮機制。
當 quicklist 很長的時候,最容易被訪問的極可能是兩端的數據,中間的數據被訪問的頻率比較低(訪問起來性能也很低)。若是應用場景符合這個特色,redis 還提供了一個選項,可以把中間的數據節點進行壓縮,從而進一步節省內存空間。Redis的配置參數 list-compress-depth
就是用來完成這個設置的。
# 列表也能夠被壓縮。
# 壓縮深度指的是列表兩側開始不須要 ziplist 節點的深度(下面會解釋)。
# 爲了執行快速的 push/pop 操做,列表的頭和尾一般不壓縮。
# 設置以下:
# 0: 禁用壓縮機制
# 1: 壓縮深度 1 表示壓縮除了頭和尾以外的全部內部節點。例如結構:
# [head]->node->node->...->node->[tail]
# 由於[head], [tail]永遠不會被壓縮,它們直接的 node 都後被壓縮。
# 2: [head]->[next]->node->node->...->node->[prev]->[tail]
# 2 表示不壓縮 head,head->next,tail->prev 和 tail, 它們以前的 node 都壓縮。
# 3: [head]->[next]->[next]->node->node->...->node->[prev]->[prev]->[tail]
# 以此類推...
list-compress-depth 0
複製代碼
這個參數默認是 0 也就是不壓縮,Redis對於 quicklist 內部節點的壓縮算法,採用的 LZF —— 一種無損壓縮算法,有興趣的能夠看下 zh.wikipedia.org/wiki/LZFSE。
以前的內容已經對源碼結構中大多數的字段作了說明,可是還遺留一些字段我在這裏統一解釋下。
首先補充一個結構 quicklistLZF,後面的說明中會出現:
typedef struct quicklistLZF {
unsigned int sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
複製代碼
屬性 | 大小 | 含義 |
---|---|---|
prev | 8字節 | 指向鏈表前一個節點的指針。 |
next | 8字節 | 指向鏈表後一個節點的指針。 |
zl | 8字節 | 數據指針。若是當前節點的數據沒有壓縮,那麼它指向一個 ziplist 結構;不然,它指向一個 quicklistLZF 結構。 |
sz | 4字節 | ziplist 佔用的字節總數。若是指向的 ziplist 被壓縮,仍然表示壓縮前的字節總數。 |
count | 16位 | ziplist 包含的元素個數。 |
encoding | 2位 | 表示ziplist是否壓縮了。目前只有兩種取值:2表示被壓縮了(並且用的是LZF壓縮算法),1表示沒有壓縮。 |
container | 2位 | 數據容器。爲1時表示 NONE,即一個 quicklist 節點下面直接存數據,爲2時表示ZIPLIST,即便用ziplist存數據。 |
recompress | 1位 | bool值,當咱們使用相似lindex這樣的命令查看了某一項原本壓縮的數據時,須要把數據暫時解壓,這時就設置 recompress = 1 作一個標記,等有機會再把數據從新壓縮。 |
attempted_compress | 1位 | bool值,在單元測試的時候用到。 |
extra | 10位 | 擴展字段,以備後用。 |
須要注意的是,我發現網上的不少文章都沒有提到的一點,官方給出的解釋中說明了 quicklistNode 是一個 32 字節的結構,這應該是針對 64 位系統而言的,由於 prev,next 和 zl 都是指針,在 64 位系統中佔 8 字節,如下的結構同理。
屬性 | 大小 | 含義 |
---|---|---|
head | 8字節 | 指向頭節點的指針。 |
tail | 8字節 | 指向尾節點的指針。 |
count | 8字節 | 全部 ziplists 的元素總個數。 |
len | 8字節 | quicklist 節點的個數。 |
fill | 16位 | ziplist大小設置,存放 list-max-ziplist-size 參數的值。 |
compress | 16位 | 節點壓縮深度,存放 list-compress-depth 參數的值。 |
另外須要注意一點的是 quicklist 結構在 64 位系統中是佔 40 個字節,可是如上計算我得出的長度是 36 字節,這裏面涉及到告終構體字節對齊約定,目的的話仍是爲了提高數據的讀取效率。
屬性 | 大小 | 含義 |
---|---|---|
sz | 4字節 | 壓縮後的ziplist大小 |
compressed | 待定 | LZF 壓縮後的數據 |
爲何 quicklistNode 中的 count 用 16 位就能夠表示?
咱們已經知道,ziplist 大小受到 list-max-ziplist-size
參數的限制。按照正值和負值有兩種狀況:
list-max-ziplist-size
參數是由quicklist結構的 fill 字段來存儲的,而 fill 字段是 16bit,因此它所能表達的值可以用 16bit 來表示。