Redis數據結構——快速列表(quicklist)


最新:Redis內存——三個重要的緩沖區

最新:Redis內存——內存消耗(內存都去哪了?)

最新:Redis持久化——如何選擇合適的持久化方式

最新:Redis持久化——AOF日志

更多文章...

之前的文章我們曾總結到了Redis數據結構——鏈表Redis數據結構——壓縮列表這兩種數據結構,他們是Redis List(列表)對象的底層實現方式。但是考慮到鏈表的附加空間相對太高,prev 和 next 指針就要占去 16 個字節 (64bit 系統的指針是 8 個字節),另外每個節點的內存都是單獨分配,會加劇內存的碎片化,影響內存管理效率。因此Redis3.2版本開始對列表數據結構進行了改造,使用 quicklist 代替了 ziplist 和 linkedlist.

一、基本結構

    quicklist 實際上是 zipList 和 linkedList 的混合體,它將 linkedList 按段切分,每一段使用 zipList 來緊湊存儲,多個 zipList 之間使用雙向指針串接起來。

typedef struct quicklistNode {
    struct quicklistNode *prev; //上一個node節點
    struct quicklistNode *next; //下一個node
    unsigned char *zl;            //保存的數據 壓縮前ziplist 壓縮后壓縮的數據
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;
  • prev: 指向鏈表前一個節點的指針。
  • next: 指向鏈表后一個節點的指針。
  • zl: 數據指針。如果當前節點的數據沒有壓縮,那么它指向一個ziplist結構;否則,它指向一個quicklistLZF結構。
  • sz: 表示zl指向的ziplist的總大小(包括zlbytes, zltail, zllen, zlend和各個數據項)。需要注意的是:如果ziplist被壓縮了,那么這個sz的值仍然是壓縮前的ziplist大小。
  • count: 表示ziplist里面包含的數據項個數。這個字段只有16bit。稍后我們會一起計算一下這16bit是否夠用。
  • encoding: 表示ziplist是否壓縮了(以及用了哪個壓縮算法)。目前只有兩種取值:2表示被壓縮了(而且用的是LZF壓縮算法),1表示沒有壓縮。
  • container: 是一個預留字段。本來設計是用來表明一個quicklist節點下面是直接存數據,還是使用ziplist存數據,或者用其它的結構來存數據(用作一個數據容器,所以叫container)。但是,在目前的實現中,這個值是一個固定的值2,表示使用ziplist作為數據容器。
  • recompress: 當我們使用類似lindex這樣的命令查看了某一項本來壓縮的數據時,需要把數據暫時解壓,這時就設置recompress=1做一個標記,等有機會再把數據重新壓縮。
  • attempted_compress: 這個值只對Redis的自動化測試程序有用。我們不用管它。
  • extra: 其它擴展字段。目前Redis的實現里也沒用上。
typedef struct quicklistLZF {
    unsigned int sz; /* LZF size in bytes*/
    char compressed[];
} quicklistLZF;

quicklistLZF結構表示一個被壓縮過的ziplist。其中:

  • sz: 表示壓縮后的ziplist大小。
  • compressed: 是個柔性數組(flexible array member),存放壓縮后的ziplist字節數組。
typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : QL_FILL_BITS;              /* fill factor for individual nodes */
    unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
    unsigned int bookmark_count: QL_BM_BITS;
    quicklistBookmark bookmarks[];
} quicklist;
  • head: 指向頭節點(左側第一個節點)的指針。
  • tail: 指向尾節點(右側第一個節點)的指針。
  • count: 所有ziplist數據項的個數總和。
  • len: quicklist節點的個數。
  • fill: 16bit,ziplist大小設置,存放list-max-ziplist-size參數的值。
  • compress: 16bit,節點壓縮深度設置,存放list-compress-depth參數的值。

二、常用操作

2.1 插入

quicklist可以選擇在頭部或者尾部進行插入(quicklistPushHeadquicklistPushTail),而不管是在頭部還是尾部插入數據,都包含兩種情況:

  • 如果頭節點(或尾節點)上ziplist大小沒有超過限制(即_quicklistNodeAllowInsert返回1),那么新數據被直接插入到ziplist中(調用ziplistPush)。
  • 如果頭節點(或尾節點)上ziplist太大了,那么新創建一個quicklistNode節點(對應地也會新創建一個ziplist),然后把這個新創建的節點插入到quicklist雙向鏈表中。

也可以從任意指定的位置插入。quicklistInsertAfterquicklistInsertBefore就是分別在指定位置后面和前面插入數據項。這種在任意指定位置插入數據的操作,要比在頭部和尾部的進行插入要復雜一些。

  • 當插入位置所在的ziplist大小沒有超過限制時,直接插入到ziplist中就好了;
  • 當插入位置所在的ziplist大小超過了限制,但插入的位置位於ziplist兩端,並且相鄰的quicklist鏈表節點的ziplist大小沒有超過限制,那么就轉而插入到相鄰的那個quicklist鏈表節點的ziplist中;
  • 當插入位置所在的ziplist大小超過了限制,但插入的位置位於ziplist兩端,並且相鄰的quicklist鏈表節點的ziplist大小也超過限制,這時需要新創建一個quicklist鏈表節點插入。
  • 對於插入位置所在的ziplist大小超過了限制的其它情況(主要對應於在ziplist中間插入數據的情況),則需要把當前ziplist分裂為兩個節點,然后再其中一個節點上插入數據。

2.2 查找

list的查找操作主要是對index的我們的quicklist的節點是由一個一個的ziplist構成的每個ziplist都有大小。所以我們就只需要先根據我們每個node的個數,從而找到對應的ziplist,調用ziplist的index就能成功找到。

2.3 刪除

區間元素刪除的函數是 quicklistDelRange

quicklist 在區間刪除時,會先找到 start 所在的 quicklistNode,計算刪除的元素是否小於要刪除的 count,如果不滿足刪除的個數,則會移動至下一個 quicklistNode 繼續刪除,依次循環直到刪除完成為止。

quicklistDelRange 函數的返回值為 int 類型,當返回 1 時表示成功的刪除了指定區間的元素,返回 0 時表示沒有刪除任何元素。

2.4 其它

除了上面介紹的基本操作之外還有一些其它操作,大家可以嘗試着根據鏈表和壓縮列表的數據結構來分析一些quicklist這些操作的時間復雜度。

操作 時間復雜度
quicklistCreate:創建 quicklist
quicklistInsertAfter:在某個元素的后面添加數據
quicklistInsertBefore:在某個元素的前面添加數據
quicklistReplaceAtIndex:替換某個元素
quicklistDelEntry:刪除單個元素
quicklistDelRange:刪除區間元素
quicklistPushHead:頭部插入元素
quicklistPushTail:尾部插入元素

小結

    Redis quicklist是Redis 3.2版本以后針對鏈表和壓縮列表進行改造的一種數據結構,是 zipList 和 linkedList 的混合體,相對於鏈表它壓縮了內存。進一步的提高了效率。

如果你有什么疑問,歡迎在評論區給我留言和分享,我會第一時間反饋!我們一起共同學習與進步!

系列文章:

最新:Redis內存——三個重要的緩沖區

最新:Redis內存——內存消耗(內存都去哪了?)

最新:Redis持久化——如何選擇合適的持久化方式

最新:Redis持久化——AOF日志

Redis持久化——內存快照(RDB)

一文回顧Redis五大對象(數據類型)

Redis對象——有序集合(ZSet)

Redis對象——集合(Set)

Redis對象——列表(List)

Redis對象——哈希(Hash)

Redis數據結構——quicklist

Redis對象——字符串

Redis對象——Redis對象系統簡介

Redis數據結構——壓縮列表

Redis數據結構——整數集合

Redis數據結構——跳躍表

Redis數據結構——字典

Redis數據結構——鏈表

Redis數據結構——簡單動態字符串SDS

-----END-----

關注下方公眾號,回復“Redis”,可得Redis相關學習資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM