Redis quicklist源码+listpack源码(6.0+以上版本)

ziplist设计上的问题,每一次增删改都需要计算前面元素的空间和长度(prevlen),这种设计缺陷非常明显,一旦其中一个entry发生修改,以这个entry后面开始,全部需要重新计算prevlen,因此诞生了连续更新的性能问题。

quicklist

quicklist实际就是双端链表,链表里的每一个节点都是ziplist,这样就可以避免减少了数据插入时内存空间的重新分配,以及内存数据的拷贝。同时每一个节点都会限制ziplist的大小,如果ziplist里面插入的entry过多,就会转化为quicklist增加node方式来存储。

基础结构(其实就是双向链表增删改查结构,没有太吸引人的地方)

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    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;

/* quicklistLZF is a 4+N byte struct holding 'sz' followed by 'compressed'.
 * 'sz' is byte length of 'compressed' field.
 * 'compressed' is LZF data with total (compressed) length 'sz'
 * NOTE: uncompressed length is stored in quicklistNode->sz.
 * When quicklistNode->zl is compressed, node->zl points to a quicklistLZF */
typedef struct quicklistLZF {
    unsigned int sz; /* LZF size in bytes*/
    char compressed[];
} quicklistLZF;

/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
 * 'count' is the number of total entries.
 * 'len' is the number of quicklist nodes.
 * 'compress' is: -1 if compression disabled, otherwise it's the number
 *                of quicklistNodes to leave uncompressed at ends of quicklist.
 * 'fill' is the user-requested (or default) fill factor. */
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 : 16;              /* fill factor for individual nodes */
    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;


/* Create a new quicklist with some default parameters. */
quicklist *quicklistNew(int fill, int compress) {
    quicklist *quicklist = quicklistCreate();
    quicklistSetOptions(quicklist, fill, compress);
    return quicklist;
}

/* Add new entry to tail node of quicklist.
 *追加元素
 * Returns 0 if used existing tail.
 * Returns 1 if new tail created. */
int quicklistPushTail(quicklist *quicklist, void *value, size_t sz) {
    quicklistNode *orig_tail = quicklist->tail;
    assert(sz < UINT32_MAX); /* TODO: add support for quicklist nodes that are sds encoded (not zipped) */
    if (likely(
            _quicklistNodeAllowInsert(quicklist->tail, quicklist->fill, sz))) {
        quicklist->tail->zl =
            ziplistPush(quicklist->tail->zl, value, sz, ZIPLIST_TAIL);
        quicklistNodeUpdateSz(quicklist->tail);
    } else {
        quicklistNode *node = quicklistCreateNode();
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_TAIL);

        quicklistNodeUpdateSz(node);
        _quicklistInsertNodeAfter(quicklist, quicklist->tail, node);
    }
    quicklist->count++;
    quicklist->tail->count++;
    return (orig_tail != quicklist->tail);
}

/* Delete one element represented by 'entry'
 *删除元素
 * 'entry' stores enough metadata to delete the proper position in
 * the correct ziplist in the correct quicklist node. */
void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
    quicklistNode *prev = entry->node->prev;
    quicklistNode *next = entry->node->next;
    int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist,
                                         entry->node, &entry->zi);

    /* after delete, the zi is now invalid for any future usage. */
    iter->zi = NULL;

    /* If current node is deleted, we must update iterator node and offset. */
    if (deleted_node) {
        if (iter->direction == AL_START_HEAD) {
            iter->current = next;
            iter->offset = 0;
        } else if (iter->direction == AL_START_TAIL) {
            iter->current = prev;
            iter->offset = -1;
        }
    }
    /* else if (!deleted_node), no changes needed.
     * we already reset iter->zi above, and the existing iter->offset
     * doesn't move again because:
     *   - [1, 2, 3] => delete offset 1 => [1, 3]: next element still offset 1
     *   - [1, 2, 3] => delete offset 0 => [2, 3]: next element still offset 0
     *  if we deleted the last element at offet N and now
     *  length of this ziplist is N-1, the next call into
     *  quicklistNext() will jump to the next node. */
}

 listpack

quicklist链表里的每一个node都会指向ziplist,内存占用极大。

listpack沿用ziplist的基础数据结构,采用的连续内存布局,不会去计算前一项的空间长度,只会计算自己的长度,这样可以完全避免连续更新的缺陷,并且做了双向索引的优化。

编码方式

listpack 元素会对不同长度的整数和字符串进行编码

整型编码类型(LP_ENCODING__XX_BIT_INT)

LP_ENCODING_7BIT_UINT   

宏定义为0,占1位,7BIT意味着可以存储7Bit无符号整数,加起来一共8位。

LP_ENCODING_13BIT_INT

宏定义为0xC0,二进制位1100 0000,前3位110表示13位编码类型,后面5位,外加1字节,所以一共存储的是13位。

LP_ENCODING_16BIT_INT (2字节无符号整数)

LP_ENCODING_24BIT_INT (3字节无符号整数)

LP_ENCODING_32BIT_INT  (4字节无符号整数)

LP_ENCODING_64BIT_INT    (8字节无符号整数)

其实以上编码类型全是宏定义来的,认识C的,应该不难理解。后续存储就是按照这些宏来判断计算。

字符串编码类型 (LP_ENCODING__XX_BIT_STR)

LP_ENCODING_6BIT_STR : 前两位10代表LP_ENCODING_6BIT_STR类型,后面6BIT保存字符串长度,长度不超过63的字符串

LP_ENCODING_12BIT_STR: 前两位1110代表LP_ENCODING_12BIT_STR类型,后面12BIT保存字符串长度,长度不超过4095的字符串

LP_ENCODING_32BIT_STR :前两位11110000代表LP_ENCODING_32BIT_STR 类型,后面32BIT保存字符串长度。

网上扣了个图,可见字符串设计的更加规范

Redis quicklist源码+listpack源码(6.0+以上版本)_第1张图片

遍历方式

正向遍历:lpFirst-->lpNext-->lpSkip  调用两个函数lpCurrentEncodedSize 和 lpEncodeBacklen

        lpCurrentEncodedSize 函数是根据当前列表项第 1 个字节的取值,来计算当前项的编码类型,并根据编码类型,计算当前项编码类型和实际数据的总长度。然后,lpEncodeBacklen 函数会根据编码类型和实际数据的长度之和,进一步计算列表项最后一部分 entry-len 本身的长度。这样一来,lpSkip 函数就知道当前项的编码类型、实际数据和 entry-len 的总长度了,也就可以将当前项指针向右偏移相应的长度,从而实现查到下一个列表项的目的。

反向遍历 :

lpPrev函数:保存指向前一项的指针,

lpDecodeBacklen 函数:计算entry-len总长度,遍历可以算出前一项的总长度。                   

        首先,我们根据 listpack 头中记录的 listpack 总长度,就可以直接定位到 listapck 的尾部结束标记。然后,我们可以调用 lpPrev 函数,lpDecodeBacklen 函数会从右向左,逐个字节地读取当前列表项的 entry-len,entry-len返回的是编码类型和实际数据的长度之和,这样就可以计算出前一项的总长度,再结合lpPrev函数指针,就可以逐步从右向左遍历逐个entry了。

你可能感兴趣的:(Redis,redis)