Redis系列(六):數據結構QuickList(快速列表)源碼解析

1.介紹

Redis在3.2版本之前List的底層編碼是ZipList和LinkedList實現的

在3.2版本之後,重新引入了QuickList的數據結構,列表的底層都是QuickList實現

當List對象中元素的長度比較小或者數量比較少的時候,採用ZipList來存儲

當List對象中元素的長度比較大或者數量比較多的時候,採用LinkList來存儲

這兩種存儲方式的優缺點

LinkedList便於在表的兩端進行Push和Pop操作,在插入節點複雜度很低,但是它的記憶體開銷很大,首先,它在每個節點上除了要保存數據之外,還要額外保存兩個指針。

其次;雙向鏈表的各個節點時單獨的記憶體塊,地址不連續,節點多了容易產生記憶體碎片

ZipList存儲在一段連續的記憶體上,所以存儲效率很高,但是它不利於修改操作,插入和刪除操作需要頻繁的申請釋放記憶體。

特別是當ZipList長度很長的時候,一次Realloc可能導致大批量的數據拷貝

QuickList可以看作一個雙向列表,但是列表的每個節點都是一個ziplist,

其實就是linkedlist和ziplist的結合。quicklist中的每個節點ziplist都能夠存儲多個數據元素。

2.示意圖

 

 

 3.源碼實現

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都能存儲多個數據項,所以有了這兩個統計值。

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的迭代器結構,指向迭代器節點元素

// quicklist的迭代器結構
typedef struct quicklistIter {
    const quicklist *quicklist;  // 指向所在quicklist的指針
    quicklistNode *current;  // 指向當前節點的指針
    unsigned char *zi;  // 指向當前節點的ziplist
    long offset; // 當前ziplist中的偏移地址
    int direction; // 迭代器的方向
} quicklistIter;
// 表示quicklist節點中ziplist里的一個節點結構
typedef struct quicklistEntry {
    const quicklist *quicklist;  // 指向所在quicklist的指針
    quicklistNode *node;  // 指向當前節點的指針
    unsigned char *zi;  // 指向當前節點的ziplist
    unsigned char *value;  // 當前指向的ziplist中的節點的字元串值
    long long longval;  // 當前指向的ziplist中的節點的整型值
    unsigned int sz;  // 當前指向的ziplist中的節點的位元組大小
    int offset;  // 當前指向的ziplist中的節點相對於ziplist的偏移量
} quicklistEntry;

創建QuickList

fill:

-5: 每個quicklist節點上的ziplist大小不能超過64 Kb。(註:1kb => 1024 bytes)

-4: 每個quicklist節點上的ziplist大小不能超過32 Kb。

-3: 每個quicklist節點上的ziplist大小不能超過16 Kb。

-2: 每個quicklist節點上的ziplist大小不能超過8 Kb。(-2是Redis給出的默認值)

-1: 每個quicklist節點上的ziplist大小不能超過4 Kb。

compress:

0 特殊值,表示不壓縮

1 表示quicklist兩端各有一個節點不壓縮,中間的節點壓縮

2 表示quicklist兩端各有兩個節點不壓縮,中間的節點壓縮

3 表示quicklist兩端各有三個節點不壓縮,中間的節點壓縮

quicklist *quicklistCreate(void) {
    struct quicklist *quicklist;  // 聲明指針
    quicklist = zmalloc(sizeof(*quicklist));  // 分配記憶體
    quicklist->head = quicklist->tail = NULL;   // 設定頭尾指針
    quicklist->len = 0;  // 設定長度
    quicklist->count = 0;  // 設定數據項總和
    quicklist->compress = 0;  // 設定壓縮深度
    quicklist->fill = -2;  // 設定ziplist大小限定
    return quicklist;
}

創建QuickListNode

REDIS_STATIC quicklistNode *quicklistCreateNode(void) {
    quicklistNode *node;
    node = zmalloc(sizeof(*node));  //  申請記憶體
    node->zl = NULL;  // 初始化指向ziplist的指針
    node->count = 0;  // 初始化數據項個數
    node->sz = 0;  // 初始化ziplist大小
    node->next = node->prev = NULL;  // 初始化prev和next指針
    node->encoding = QUICKLIST_NODE_ENCODING_RAW;  // 初始化節點編碼方式
    node->container = QUICKLIST_NODE_CONTAINER_ZIPLIST;  // 初始化存放數據的方式
    node->recompress = 0;  // 初始化再壓縮標記
    return node;
}

 

Tags: