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

系列文章

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

最新:Redis持久化——AOF日誌node

最新:Redis持久化——內存快照(RDB)redis

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

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

Redis對象——集合(Set)數據結構

Redis對象——列表(List)運維

Redis對象——哈希(Hash)函數

Redis數據結構——quicklist學習

Redis對象——字符串測試

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

Redis數據結構——壓縮列表

Redis數據結構——整數集合

Redis數據結構——跳躍表

Redis數據結構——字典

Redis數據結構——鏈表

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

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

1、基本結構

    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、經常使用操做

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官方文檔》

-----END-----

關注下方公衆號,回覆「Redis」,可得Redis相關學習資料

相關文章
相關標籤/搜索