深入分析redis之quicklist,不一样的ziplist使用方式?

本文解析Redis中的quicklist数据结构,介绍了其原理、组成部分(如Node、Entry、Iter)、数据压缩与解压缩方法,并详细探讨了插入、删除、更改和查找操作的优化策略。重点讨论了quicklist如何在内存效率和时间效率之间取得平衡,以及如何通过配置参数适应不同场景的需求。


前言

续接上文:redis压缩列表ziplist,内存优化之路

本文参考源码版本仍为redis6.2


quicklist是Redis底层最重要的数据结构之一,它是Redis对外提供的6种基本数据结构中List的底层实现,在Redis 3.2版本中引入。

在引入quicklist之前,Redis采用压缩链表(ziplist)以及双向链表(linked-list)作为List的底层实现。

  • 当元素个数比较少并且元素长度比较小时,Redis采用ziplist作为其底层存储。
  • 当任意一个条件不满足时,Redis采用linked-list作为底层存储结构。

这么做的主要原因是,当元素长度较小时,采用ziplist可以有效节省存储空间,但ziplist的存储空间是连续的,当元素个数比较多时,修改元素时,必须重新分配存储空间,这无疑会影响Redis的执行效率,故而采用一般的双向链表。

quicklist是综合考虑了时间效率与空间效率引入的新型数据结构。


一、quicklist真面目

Redis源码中对quicklist的注释为 A doubly linked list of ziplists;也就是说quicklist是由ziplist组成的双向链表,其中每一个链表节点都是一个独立的ziplist结构,因此,从结构上看,quicklist就是ziplist的升级版。

优化的关键在于,如何控制好每个ziplist的大小

  • quicklist的节点ziplist越小,越有可能造成更多的内存碎片。极端情况下,一个ziplist只有一个数据entry,也就退化成了linked list
  • quicklist的节点ziplist越大,分配给ziplist的连续内存空间越困难。极端情况下,一个quicklist只有一个ziplist,也就退化成了ziplist

因此,合理配置参数显得至关重要,不同场景可能需要不同配置;redis提供list-max-ziplist-size参数进行配置,默认-2,表示每个ziplist节点大小不超过8KB

二、原理分析

数据存储

1. quicklistNode结构:

typedef struct quicklistNode {
   
   
    struct quicklistNode *prev;  //前一个quicklistNode
    struct quicklistNode *next;  //后一个quicklistNode
    unsigned char *zl;           //quicklistNode指向的ziplist
    unsigned int sz;             //ziplist的字节大小
    unsigned int count : 16;     //ziplist中的元素个数
    unsigned int encoding : 2;   //编码格式,原生字节数组或压缩存储
    unsigned int container : 2;  //存储方式
    unsigned int recompress : 1; //数据是否被压缩
    unsigned int attempted_compress : 1; //数据能否被压缩
    unsigned int extra : 10;     //预留的bit位
} quicklistNode;

其中

  • prev、next指向该节点的前后节点;
  • zl指向该节点对应的ziplist结构;
  • sz代表整个ziplist结构的大小;
  • encoding代表采用的编码方式:1代表是原生的,2代表使用LZF进行压缩;
  • container为quicklistNode节点zl指向的容器类型:1代表none,2代表使用ziplist存储数据
  • recompress代表这个节点之前是否是压缩节点,若是,则在使用压缩节点前先进行解压缩,使用后需要重新压缩,此外为1,代表是压缩节点;
  • attempted_compress测试时使用;
  • extra为预留

2. quicklist结构

quicklist 作为一个链表结构,在它的数据结构中,是定义了整个 quicklist 的头、尾指针,这样一来,可以通过 quicklist 的数据结构,来快速定位到 quicklist 的链表头和链表尾。

typedef struct quicklist {
   
   
    quicklistNode *head;   // quicklist的链表头
    quicklistNode *tail;   // quicklist的链表尾
    unsigned long count;   // 所有ziplist中的总元素个数
    unsigned long len;     // quicklistNodes的个数
    int fill : QL_FILL_BITS;  // 单独解释
    unsigned int compress : QL_COMP_BITS; // 具体含义是两端各有compress个节点不压缩
    ...
} quicklist;

fill用来指明每个quicklistNode中ziplist长度,当fill为正数时,表明每个ziplist最多含有的数据项数,当fill为负数时,如下:

  • Length -1: 4k,即ziplist节点最大为4KB
  • Length -2: 8k,即ziplist节点最大为8KB
  • Length -3: 16k …
  • Length -4: 32k
  • Length -5: 64k

fill取负数时,必须大于等于-5。可以通过Redis修改参数list-max-ziplist-size配置节点所占内存大小。实际上每个ziplist节点所占的内存会在该值上下浮动。

考虑quicklistNode节点个数较多时,我们经常访问的是两端的数据,为了进一步节省空间,Redis允许对中间的quicklistNode节点进行压缩,通过修改参数list-compress-depth进行配置,即设置compress参数,该项的具体含义是两端各有compress个节点不压缩。

quicklist整体结构:

3. quicklistEntry结构

quicklistNode中ziplist中的一个节点

typedef struct quicklistEntry {
   
   
    const quicklist *quicklist;
    quickl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柏油

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值