Redis數據結構(2)——列表

列表

內部編碼:

  • 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結構示意圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章