《閒扯Redis五》List資料型別底層之quicklist

jstarseven發表於2020-04-24

一、前言

Redis 提供了5種資料型別:String(字串)、Hash(雜湊)、List(列表)、Set(集合)、Zset(有序集合),理解每種資料型別的特點對於redis的開發和運維非常重要。

原文解析

Redis五種資料型別

Redis 中的 list 是我們經常使用到的一種資料型別,根據使用方式的不同,可以應用到很多場景中。

二、底層解析

1、上節回顧

 上節《閒扯Redis四》List資料型別底層編碼轉換 說道,在 3.0 版本的 Redis 中,List 型別有兩種實現方式:

1、使用壓縮列表(ziplist)實現的列表物件。

2、使用雙端連結串列(linkedlist)實現的列表物件。

在 3.2 版本後新增了 quicklist 資料結構實現了 list,現在就來分析下 quicklist 的結構

2、官方描述

 先來看看 Redis 官方對 quicklist 的描述:

    A doubly linked list of ziplists

    A generic doubly linked quicklist implementation

 可見 quicklist 是一個雙向連結串列,並且是一個 ziplist 的雙向連結串列,也就是說 quicklist 的每個節點都是一個 ziplist。而通過前面的文章我們們可以知道,ziplist 本身也是一個能維持資料項先後順序的列表,而且資料項儲存在一個連續的記憶體塊中。那是不是意味著 quicklist 結合了壓縮列表和雙端連結串列的特點呢!

Redis五種資料型別

3、結構分析

quicklist 結構定義

/* 
 * quicklist
 */
typedef struct quicklist {
    //頭結點
    quicklistNode *head; 
    //尾節點
    quicklistNode *tail; 
    //所有ziplist中entry數量
    unsigned long count; 
    //quicklistNodes節點數量
    unsigned int len;   
    //ziplist中entry能儲存的數量,由list-max-ziplist-size配置項控制 
    int fill : 16;       
    //壓縮深度,由list-compress-depth配置項控制
    unsigned int compress : 16; 
} quicklist;

quicklist 結構屬性註釋

註釋:

fill :ziplist 中 entry 能儲存的數量,由 list-max-ziplist-size 配置項控制

    表示了單個節點(quicklistNode)的負載比例(fill factor),負數限制 quicklistNode 中的 ziplist 的位元組長度, 
    正數限制 quicklistNode 中的 ziplist 的最大長度。
-5: 最大儲存空間: 64 Kb <-- 通常情況下不要設定這個值
-4: 最大儲存空間: 32 Kb <-- 非常不推薦
-3: 最大儲存空間: 16 Kb <-- 不推薦
-2: 最大儲存空間: 8 Kb <-- 推薦
-1: 最大儲存空間: 4 Kb <-- 推薦
對於正整數則表示最多能儲存到你設定的那個值, 當前的節點就裝滿了
通常在 -2 (8 Kb size) 或 -1 (4 Kb size) 時, 效能表現最好

compress :壓縮深度,由 list-compress-depth 配置項控制

    表示 quicklist 中的節點 quicklistNode, 除開最兩端的 compress 個節點之後, 中間的節點都會被壓縮(LZF壓縮演算法)。

quicklistNode 結構定義

typedef struct quicklistNode {
    //前節點指標
    struct quicklistNode *prev; 
    //後節點指標
    struct quicklistNode *next; 
    //資料指標。當前節點的資料沒有壓縮,那麼它指向一個ziplist結構;否則,它指向一個quicklistLZF結構。
    unsigned char *zl;
    //zl指向的ziplist實際佔用記憶體大小。需要注意的是:如果ziplist被壓縮了,那麼這個sz的值仍然是壓縮前的ziplist大小
    unsigned int sz;  
    //ziplist裡面包含的資料項個數
    unsigned int count : 16;   
    //ziplist是否壓縮。取值:1--ziplist,2--quicklistLZF 
    unsigned int encoding : 2; 
    //儲存型別,目前使用固定值2 表示使用ziplist儲存
    unsigned int container : 2; 
    //當我們使用類似lindex這樣的命令檢視了某一項本來壓縮的資料時,需要把資料暫時解壓,這時就設定recompress=1做一個標記,等有機會再把資料重新壓縮
    unsigned int recompress : 1;
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

quicklistLZF 結構定義

typedef struct quicklistLZF {
    unsigned int sz;  //壓縮後的ziplist大小
    char compressed[];//柔性陣列,存放壓縮後的ziplist位元組陣列
} quicklistLZF;

4、quicklist 結構圖

 根據上述結構體定義,我們們可以繪製一下 quicklist 的結構:

Redis五種資料型別

三、要點總結

1、雙端連結串列

1.雙端連結串列便於在表的兩端進行 push 和 pop 操作,但是它的記憶體開銷比較大;

2.雙端連結串列每個節點上除了要儲存資料之外,還要額外儲存兩個指標;

3.雙端連結串列的各個節點是單獨的記憶體塊,地址不連續,節點多了容易產生記憶體碎片;

2、壓縮列表

1.ziplist 由於是一整塊連續記憶體,所以儲存效率很高;

2.ziplist 不利於修改操作,每次資料變動都會引發一次記憶體的 realloc;

3.當 ziplist 長度很長的時候,一次 realloc 可能會導致大批量的資料拷貝,進一步降低效能;

3、quicklist

1.空間效率和時間效率的折中;

2.結合了雙端連結串列和壓縮列表的優點;

Redis五種資料型別

相關文章