列表
內部編碼:
- ziplist(壓縮列表):當列表元素個數小於 list-max-ziplist-entries 配置(默認512個),同時列表中每個元素的值都小於list-max-ziplist-value配置時(默認64個字節),redis會選用ziplist來作用列表內部實現(3.2版本前)
- linkedlist(雙向鏈表):當列表類型無法滿足ziplist條件時(3.2版本前)
-
quicklist(快速列表):3.2版本及之後提供的新數據結構,中和ziplist和linkedlist的優缺點
作用:鏈表、發佈與訂閱、慢查詢、監視器
linkedlist實現:
linkedlist節點代碼:
typedef struct listNode {
// 前置節點
struct listNode *prev;
// 後置節點
struct listNode *next;
// 節點的值
void *value;
}listNode;
鏈表linkedlist結構:
typedef struct list {
// 表頭節點
listNode *head;
// 表尾節點
listNode *tail;
// 鏈表所包含的節點數量
unsigned long len;
// 節點值複製函數
void *(*dup)(void *ptr);
// 節點值釋放函數
void *(*free)(void *ptr);
// 節點值對比函數
int (*match)(void *ptr, void *key);
} list;
鏈表linkedlist示意圖:
quicklist實現
早期版本在這邊選擇的折中方案是兩種數據類型的轉換,但是在3.2版本之後,引入了一種新的數據格式,結合了雙向列表linkedlist和ziplist的特點,稱之爲quicklist(快速列表)。所有的節點都用quicklist存儲,省去了到臨界條件是的格式轉換。
那麼quicklist是一種什麼樣的格式呢?簡單的說,我們仍舊可以將其看作一個雙向列表,但是列表的每個節點都是一個ziplist,其實就是linkedlist和ziplist的結合。quicklist中的每個節點ziplist都能夠存儲多個數據元素。
quicklist的定義如下:
typedef struct quicklist {
quicklistNode *head; // 指向quicklist的頭部
quicklistNode *tail; // 指向quicklist的尾部
unsigned long count; // 列表中所有數據項的個數總和
unsigned int len; // quicklist節點的個數,即ziplist的個數
int fill : 16; // ziplist大小限定,由list-max-ziplist-size給定
unsigned int compress : 16; // 節點壓縮深度設置,由list-compress-depth給定
} quicklist;
可以看到,這邊其實有兩個統計值,count用來統計所有數據項的個數總和,len用來統計quicklist的節點個數, 因爲每個節點ziplist都能存儲多個數據項,所以有了這兩個統計值。
quicklist的節點node的定義如下:
quicklist的節點node的定義如下:
typedef struct quicklistNode {
struct quicklistNode *prev; // 指向上一個ziplist節點
struct quicklistNode *next; // 指向下一個ziplist節點
unsigned char *zl; // 數據指針,如果沒有被壓縮,就指向ziplist結構,反之指向quicklistLZF結構//
unsigned int sz; // 表示指向ziplist結構的總長度(內存佔用長度)
unsigned int count : 16; // 表示ziplist中的數據項個數
unsigned int encoding : 2; // 編碼方式,1--ziplist,2--quicklistLZF
unsigned int container : 2; // 預留字段,存放數據的方式,1--NONE,2--ziplist
unsigned int recompress : 1; // 解壓標記,當查看一個被壓縮的數據時,需要暫時解壓,標記此參數爲1,之後再重新進行壓縮//
unsigned int attempted_compress : 1; // 測試相關
unsigned int extra : 10; // 擴展字段,暫時沒用
} quicklistNode;
通過quicklist 將quicklistNode連接起來就是一個完整的雙向列表了。爲了進一步節約空間,Redis 還會對 ziplist 進行壓縮存儲,使用 LZF 算法壓縮,可以選擇壓縮深度(quicklist compress字段表示)。
每個 quicklistNode存多少元素?
由於quicklist結構包含了壓縮表和鏈表,那麼每個quicklistNode的大小就是一個需要仔細考量的點。如果單個quicklistNode存儲的數據太多,就會影響插入效率;但是如果單個quicklistNode太小,就會變得跟鏈表一樣造成空間浪費。
quicklist 內部默認單個 ziplist 長度爲 8k 字節,超出了這個字節數,就會新起一個 ziplist。ziplist 的長度由配置參數list-max-ziplist-size決定。
quicklist通過fill對單個quicklistNode的大小進行限制:fill可以被賦值爲正整數或負整數:
- -1:單個節點最多存儲4kb
- -2:單個節點最多存儲8kb
- -3:單個節點最多存儲16kb
- -4:單個節點最多存儲32kb
- -5:單個節點最多存儲64kb
- 爲正數時,表示單個節點最大允許的元素個數,最大爲32768個
壓縮深度:
上文中提到了一個LZF的壓縮算法,該算法用於對quicklist的節點進行壓縮操作。list的設計目的是能夠存放很長的數據列表,當列表很長時,必然會佔用很高的內存空間,且list中最容易訪問的是兩端的數據,中間的數據訪問率較低,於是就可以從這個出發點來進一步節省內存用於其他操作。Redis提供了一下的配置參數,用於表示中間節點是否壓縮(quicklist compress字段表示)。
list-compress-depth 0 //默認0
- 0 特殊值,表示不壓縮
- 1 表示quicklist兩端各有一個節點不壓縮,中間的節點壓縮
- 2 表示quicklist兩端各有兩個節點不壓縮,中間的節點壓縮
- 3 表示quicklist兩端各有三個節點不壓縮,中間的節點壓縮
- 以此類推。
quicklist結構示意圖: