Redis 3.0.4 压缩列表

  压缩列表是列表键和哈希键的底层实现之一。

  1.压缩列表的构成

    1.1.ziplist结构如图  

    1. zlbytes(4字节) : 表示压缩表的总长;
    2. zltail(4字节):表示压缩列表表尾节点距离列表的起始地点有多少字节;
    3. zllen(2字节):表示压缩列表包含的节点数量(当值小于uint16_max 65535时,节点的真实数量需要遍历整个压缩列表才能得出);
    4. entryX(不定):表示压缩列表的各个节点节点的长度由节点保存的内容决定;
    5. zlend(1字节):特殊值0xFF(255),用于标记压缩列表的末尾。
//将zl定义到前四个字节的bytes成员 记录整个压缩列表的内存字节数
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))
//将zl定位到4字节到8字节的tail_offset成员 记录整个压缩列表的内存字节数
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))
//将zl定位到8字节到10字节的length成员,记录着压缩列表的节点数量
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))
//压缩列表表头的大小10个字节
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))
//返回压缩列表首节点的地址
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)
//返回压缩列表尾节点的地址
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))
//返回end成员的地址 一个字节
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

    1.2.创建一个空的压缩列表

//压缩列表总共有11个字节的固定长度
//创建并返回一个新的压缩列表
unsigned char *ziplistNew(void) {
    //ZIPLIST_HEADER_SIZE 是压缩列表的表头 1字节是末端的end大小
    unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
    unsigned char *zl = zmalloc(bytes);  //为表头和表尾end成员分配空间
    ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);   //将bytes成员初始化为bytes=11
    ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE); //空列表的tail_offset成员为表头大小为10字节
    ZIPLIST_LENGTH(zl) = 0; //字节数量为0
    zl[bytes-1] = ZIP_END;   //将表尾end成员设置默认的255
    return zl;
}

  2.压缩列表节点的构成

typedef struct zlentry {
    //prevrawlen 前驱节点的长度
    //prevrawlensize 编码前驱节点的长度prevrawlen所需的字节大小
    unsigned int prevrawlensize, prevrawlen;
    //len 当前节点值长度
    //lensize 编码当前节点长度len所需的字节数
    unsigned int lensize, len;
    //当前节点header的大小 = lensize + prevrawlensize
    unsigned int headersize;
    //当前节点的编码方式
    unsigned char encoding;
    //指向当前节点的指针,以char *类型保存
    unsigned char *p;
} zlentry; //压缩列表节点信息的结构

    压缩列表节点的定义是zlentry,但是实质上存储节点是利用了以下的结构

      1. previous_entry_length:记录前驱节点的长度;
      2. encoding:即记录当前节点的value成员的数据类型及长度;
      3. value:根据encoding来保存字节数组或者整数。 

      2.1 prevois_entry_length

        记录前驱节点的长度,以字节为单位。长度可以是1字节或者5字节。

        1>. 如果前一字节的长度小于254,那么previous_entry_length长度为1字节,

        2>.如果前一字节的长度大于等于254,那么previous_entry_length长度为5字节,其中第一个字节是0XFE(254),而之后的四个字节表示前一节点的长度。

      2.2 encoding

      记录了节点的content属性所保存数据的类型以及长度。

        1.一字节、两字节或者五字节,值的最高位为00、01、10的字节数组编码,这种编码表示节点的content属性保存着字节数组,字节数组的长度由编码出掉最高两位之后的其他记录。

        2.一字节长,值的最高位以11开头的是整数编码:这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他记录。

      2.3 content

        节点的content属性负责保存节点的值,节点值可以是一个节点数组或者整数,值的类型和长度由节点的encoding属性决定。

  3.连锁更新

    在压缩列表节点的结构里previous_entry_length是记录前一个节点的长度,如果前一个节点的长度小于254,那么previous_entry_length需要1个字节空间保存前驱节点的长度值,如果前一个节点的长度大于等于254,那么previous_entry_length需要5个节点空间保存前驱节点的长度值。

    假设存在一种情况,压缩列表里面的节点长度都是小于254字节,e1~eN字节数都在250~253之间,这时有一个长度大于254字节的new节点,需要插入到e1前,插入之后由于e1节点的previoud_entry_length是一个字节的,不能保存new节点的字节长度,需要碎e1节点进行扩展,同理,e1扩展之后字节大小大于等于254,则e1的后继节点e2节点的previous_entry_length不能保存e1扩展知乎的字节长度,也需要对e2扩展,以此类推,之后的节点都需要扩展,所以连锁更新最坏的情况下需要对压缩列表执行N次空间重新分配操作,而每次空间分配操作的最坏的时间复杂度是O(n),所以连锁更新最坏的时间复杂度是O(N^2)。

    如果e1~eN字节数都是大于254,此时有一个长度小于254的new节点需要插入到e1前,也会产生连锁更新,并且在删除节点也会有连锁更新。

  连锁更新的代码如下:

//将列表zk的p指针针对的next节点进行移动
//
static unsigned char *__ziplistCascadeUpdate(unsigned char *zl, unsigned char *p) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), rawlen, rawlensize; //获取当前列表的长度
    size_t offset, noffset, extra;
    unsigned char *np;
    zlentry cur, next;
    //ZIP_END标识尾节点
    while (p[0] != ZIP_END) {
        cur = zipEntry(p);
        rawlen = cur.headersize + cur.len;
        rawlensize = zipPrevEncodeLength(NULL,rawlen); //计算当前节点长度所需的字节数

        /* Abort if there is no next entry. */
        if (p[rawlen] == ZIP_END) break;
        next = zipEntry(p+rawlen);  //获取p的next节点

        /* Abort when "prevlen" has not changed. */
        if (next.prevrawlen == rawlen) break;
        //如果next节点存储的前驱节点长度所需的字节大小 小于 前驱节点所需的字节大小
        if (next.prevrawlensize < rawlensize) {
            /* The "prevlen" field of "next" needs more bytes to hold
             * the raw length of "cur". */
            offset = p-zl; //p节点的偏移量
            extra = rawlensize-next.prevrawlensize; //需要额外增长的字节大小
            zl = ziplistResize(zl,curlen+extra); //resize 链表所需空间大小
            p = zl+offset;

            /* Current pointer and offset for next element. */
            np = p+rawlen;  //next节点的新地址
            noffset = np-zl;  //next节点的偏移量
            //调整列表zltail的大小
            /* Update tail offset when next element is not the tail element. */
            if ((zl+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))) != np) {
                ZIPLIST_TAIL_OFFSET(zl) =
                    intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+extra);
            }

            /* Move the tail to the back. */
            //更新 移动next节点除header的prevrawlensize之外的剩余list的内存
            //从next节点的prev_entry_len字段之后的内存开始 到zlend之前的内容 
            memmove(np+rawlensize,
                np+next.prevrawlensize,
                curlen-noffset-next.prevrawlensize-1);
            //更新  将next节点的header以rawlen长度重新编码,更新prevrawlensize和prevrawlen 
            //更新next节点的previous_entry_length字段为rawlen
            zipPrevEncodeLength(np,rawlen);

            /* Advance the cursor */
            p += rawlen;
            curlen += extra;
        } else {
            if (next.prevrawlensize > rawlensize) {
                /* This would result in shrinking, which we want to avoid.
                 * So, set "rawlen" in the available bytes. */
                zipPrevEncodeLengthForceLarge(p+rawlen,rawlen);
            } else {
                zipPrevEncodeLength(p+rawlen,rawlen);
            }

            /* Stop here, as the raw length of "next" has not changed. */
            break;
        }
    }
    return zl;
}

  是根据给定的zl节点,然后通过更新p节点针对的next节点进行移动。首先获取p节点的字节长度和长度,来和p的next节点保存的previous_entry_length比较大小,如果next.prevrawlensize < rawlensize,那么需要对next.prev_entry_len字段之后的内存开始,到zlend之前的内容,进行memmove,来为next节点记录其前驱节点获取到足够的字节大小。

  4.删除节点

  从ziplist中,p指向的节点开始,最多删除num个节点

//从p节点开始最多删除num个节点
static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsigned int num) {
    unsigned int i, totlen, deleted = 0;
    size_t offset;
    int nextdiff = 0;
    zlentry first, tail;

    first = zipEntry(p);
    for (i = 0; p[0] != ZIP_END && i < num; i++) {
        p += zipRawEntryLength(p); //需要删除的总字节数
        deleted++; //需要删除的节点数
    }

    totlen = p-first.p; //需要删除的总字节数
    if (totlen > 0) {
        if (p[0] != ZIP_END) {
            /* Storing `prevrawlen` in this entry may increase or decrease the
             * number of bytes required compare to the current `prevrawlen`.
             * There always is room to store this, because it was previously
             * stored by an entry that is now being deleted. */
            nextdiff = zipPrevLenByteDiff(p,first.prevrawlen);
            p -= nextdiff;  
            zipPrevEncodeLength(p,first.prevrawlen);
            
            /* Update offset for tail */
            //更新zltail  表尾据压缩列表的起始地址的字节数
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))-totlen);
            
            /* When the tail contains more than one entry, we need to take
             * "nextdiff" in account as well. Otherwise, a change in the
             * size of prevlen doesn't have an effect on the *tail* offset. */
            tail = zipEntry(p);
            if (p[tail.headersize+tail.len] != ZIP_END) {
                ZIPLIST_TAIL_OFFSET(zl) =
                   intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
            }
            
            /* Move tail to the front of the ziplist */
            //移动p节点到zlend节点至first.p 
            memmove(first.p,p,
                intrev32ifbe(ZIPLIST_BYTES(zl))-(p-zl)-1);
        } else {
            /* The entire tail was deleted. No need to move memory. */
            //p节点之后的所有元素都删除了
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe((first.p-zl)-first.prevrawlen);
        }
        * Resize and update length */
        offset = first.p-zl;
        zl = ziplistResize(zl, intrev32ifbe(ZIPLIST_BYTES(zl))-totlen+nextdiff);
        ZIPLIST_INCR_LENGTH(zl,-deleted);
        p = zl+offset;

        /* When nextdiff != 0, the raw length of the next entry has changed, so
         * we need to cascade the update throughout the ziplist */
        //表示p节点的prev_raw_len_size需要扩容或者缩容  需要连锁更新 
       if (nextdiff != 0)
            zl = __ziplistCascadeUpdate(zl,p);
    }
    return zl;
}

  删除节点,需要知道要删除的字节长度,和节点数,需要处理的是如果删除完之后的后续节点不是zlend,则要更新p节点的prevrawlen,并且更新zltail的长度。

  5.插入节点

    插入到p所指向的节点

/* Insert item at "p". */
//插入到p所指向的节点
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
    size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
    unsigned int prevlensize, prevlen = 0;
    size_t offset;
    int nextdiff = 0;
    unsigned char encoding = 0;
    long long value = 123456789; /* initialized to avoid warning. Using a value
                                    that is easy to see if for some reason
                                    we use it uninitialized. */
    zlentry tail;


    /* Find out prevlen for the entry that is inserted. */
    //获取p的前继节点的长度prevlen
    if (p[0] != ZIP_END) {
        ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
    } else {
        unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
        if (ptail[0] != ZIP_END) {
            prevlen = zipRawEntryLength(ptail);
        }
    }

    /* See if the entry can be encoded */
    //计算s节点的value encoding
    if (zipTryEncoding(s,slen,&value,&encoding)) {
        /* 'encoding' is set to the appropriate integer encoding */
        reqlen = zipIntSize(encoding);
    } else {
        /* 'encoding' is untouched, however zipEncodeLength will use the
         * string length to figure out how to encode it. */
        reqlen = slen;
    }
    /* We need space for both the length of the previous entry and
     * the length of the payload. */
    //获取新节点的总长度
    reqlen += zipPrevEncodeLength(NULL,prevlen);
    reqlen += zipEncodeLength(NULL,encoding,slen);

    /* When the insert position is not equal to the tail, we need to
     * make sure that the next entry can hold this entry's length in
     * its prevlen field. */
    //c节点要插入到p节点的前面  计算p的prev_entry_len 和要插入的s节点的节点长度的差值
    nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;

    /* Store offset because a realloc may change the address of zl. */
    offset = p-zl;
    zl = ziplistResize(zl,curlen+reqlen+nextdiff);
    p = zl+offset;

    /* Apply memory move when necessary and update tail offset. */
    if (p[0] != ZIP_END) {
        /* Subtract one because of the ZIP_END bytes */
        //p+reqlen表示p节点的新地址  nextdiff表示前继节点是否需要增大或者减少  
        //curlen-offset-1+nextdiff  表示移动长度
        memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);

        /* Encode this entry's raw length in the next entry. */
        //更新移动之后的p节点的prev_raw_length
        zipPrevEncodeLength(p+reqlen,reqlen);

        /* Update offset for tail */
        //更新 zltail
        ZIPLIST_TAIL_OFFSET(zl) =
            intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);

        /* When the tail contains more than one entry, we need to take
         * "nextdiff" in account as well. Otherwise, a change in the
         * size of prevlen doesn't have an effect on the *tail* offset. */
        tail = zipEntry(p+reqlen);
        if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {
            ZIPLIST_TAIL_OFFSET(zl) =
                intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);
        }
    } else {
        /* This element will be the new tail. */
        ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);
    }

    /* When nextdiff != 0, the raw length of the next entry has changed, so
     * we need to cascade the update throughout the ziplist */
    if (nextdiff != 0) {
        offset = p-zl;
        zl = __ziplistCascadeUpdate(zl,p+reqlen);
        p = zl+offset;
    }

    /* Write the entry */
    p += zipPrevEncodeLength(p,prevlen);
    p += zipEncodeLength(p,encoding,slen);
    if (ZIP_IS_STR(encoding)) {
        memcpy(p,s,slen);
    } else {
        zipSaveInteger(p,value,encoding);
    }
    ZIPLIST_INCR_LENGTH(zl,1);
    return zl;
}

      

原文地址:https://www.cnblogs.com/chenyang920/p/13205314.html