美文网首页
搞懂Redis(二)-2:List数据结构

搞懂Redis(二)-2:List数据结构

作者: 高19 | 来源:发表于2022-04-14 17:05 被阅读0次

    List存储结构

    1. Redis3.2前,底层是用压缩列表zipList\双向循环链表linkedList
      当list存储的数据量比较少,且同时满足下面两个条件时,list就使用zipList存储数据
    • list保存的每个元素长度小于64字节
    • 列表中数据个数少于512个
    1. Redis3.2及之后的底层实现方式:quickList
      quickList是双向链表,而且是一个基于zipList的双向链表,quickList的每个节点都是一个zipList,结合了双向链表和zipList的优点.
    压缩列表(zipList)

    压缩链表. 好处:节省内存空间,因为它存储的内容都是在连续的内存区域当中的.当列表对象元素不大,每个元素也不大的时候,就采用zipList存储. 但当数据量过大时,zipList就不那么好用了. 因为为了保证它存储内容在内存中的连续性,插入的复杂度为O(N),即每次插入都会重新进行realloc.
    zipList的结构如下:


    zipList的数据结构
    1. zlbytes: 用于记录整个压缩列表占用的内存字节数.
    2. zltail:记录列表尾节点距离压缩列表的起始地址有多少字节
    3. zllen:记录了压缩列表包含的节点数量
    4. entryX:列表包含的各个节点
    5. zlend:用于标记压缩列表的末端
    为什么数据量大的时候不用zipList?

    因为zipList是一段连续的内存,插入的时间复杂度O(n),而且每当插入新的元素需要realloc做内存拓展;而且如果超出zipList内存大小,还会做重新分配的内存空间,并将内容复制到新的地址. 如果数量大的话,重新分配内存和拷贝内存会消耗大量时间. 所以不适合大型字符串,也不适合存储量多的元素.

    快速列表(quickList)

    是zipList和linkedList的混合体,是将linkedList按段切分,每一段用zipList来紧凑存储,多个zipList之间使用双向指针链接.

    为什么不直接使用linkedList?

    linkedList的附加空间相对太高,prev和next指针就要占去16字节,而且每个节点都是单独分配,会加剧内存的碎片化,影响内存管理效率.
    quickList结构如下

    typedef struct quicklist {
        // 指向quicklist的头部
        quicklistNode *head;
        // 指向quicklist的尾部
        quicklistNode *tail;
        unsigned long count;
        unsigned int len;
        // ziplist大小限定,由list-max-ziplist-size给定
        int fill : 16;
        // 节点压缩深度设置,由list-compress-depth给定
        unsigned int compress : 16;
    } quicklist;
    
    typedef struct quicklistNode {
        // 指向上一个ziplist节点
        struct quicklistNode *prev;
        // 指向下一个ziplist节点
        struct quicklistNode *next;
        // 数据指针,如果没有被压缩,就指向ziplist结构,反之指向quicklistLZF结构
        unsigned char *zl;
        // 表示指向ziplist结构的总长度(内存占用长度)
        unsigned int sz;
        // ziplist数量
        unsigned int count : 16;     /* count of items in ziplist */
        unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
        // 预留字段,存放数据的方式,1--NONE,2--ziplist
        unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
        // 解压标记,当查看一个被压缩的数据时,需要暂时解压,标记此参数为1,之后再重新进行压缩
        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;
    
    typedef struct quicklistLZF {
        // LZF压缩后占用的字节数
        unsigned int sz; /* LZF size in bytes*/
        // 柔性数组,存放压缩后的ziplist字节数组
        char compressed[];
    } quicklistLZF;
    

    结构图如下:


    quickList结构图

    zipList的长度
    内部默认单个zipList的长度为8K字节,超出了这个字节数,就会新起一个zipList.关于长度可以使用list-max-zipList-size决定.

    压缩深度
    quickList是由多个zipList组成的,同时为了进一步节省空间,Redis还会对zipList进行压缩存储,使用LZF算法压缩,可以选择压缩深度.quickList默认压缩深度为0,即不压缩. 压缩的实际深度由配置参数list-compress-depth决定.为了支持快速push/pop操作,quickList的首尾两个zipList不压缩,此时深度就是1.如果深度为2,就表示quickList的首尾第一个zipList以及首尾第二个zipList都不压缩.

    相关文章

      网友评论

          本文标题:搞懂Redis(二)-2:List数据结构

          本文链接:https://www.haomeiwen.com/subject/aditertx.html