redis quicklist

简介

A generic doubly linked quicklist implementation
A doubly linked list of ziplists
根据源代码里面给我出的解释就是这个是一个ziplist组成的双向链表。
每个节点使用ziplist来保存数据。
根据咱们的编程经验来说。一个链表使用另外一种链表来作为节点,那么本质上来说,quicklist里面保存着一个一个小的ziplist。
但是为啥不直接用ziplist喃。本身ziplist就是一个双向链表。并且额外的信息还特别的少。
其实原因很简单。根据我们上一节讲的ziplist来看,ziplist在我们程序里面来看将会是一块连续的内存块。它使用内存偏移来保存next从而节约了next指针。这样造成了我们每一次的删除插入操作都会进行remalloc,从而分配一块新的内存块。当我们的ziplist特别大的时候。没有这么大空闲的内存块给我们的时候。操作系统其实会抽象出一块连续的内存块给我。在底层来说他其实是一个链表链接成为的内存。不过在我们程序使用来说。他还是一块连续的内存。这样的话会造成内存碎片,并且在操作的时候因为内存不连续等原因造成效率问题。或者因为转移到大内存块等进行数据迁移。从而损失性能。
所以quicklist是对ziplist进行一次封装,使用小块的ziplist来既保证了少使用内存,也保证了性能。

数据结构

/* quicklistNode is a 32 byte struct describing a ziplist for a quicklist.
 * We use bit fields keep the quicklistNode at 32 bytes.
 * count: 16 bits, max 65536 (max zl bytes is 65k, so max count actually < 32k).
 * encoding: 2 bits, RAW=1, LZF=2.
 * container: 2 bits, NONE=1, ZIPLIST=2.
 * recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
 * attempted_compress: 1 bit, boolean, used for verifying during testing.
 * extra: 12 bits, free for future use; pads out the remainder of 32 bits */
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;

/* 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 32 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 int 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;

说起来这quickList就是一个标准的双向链表的配置,有head 有tail node里面有prev 有next 其他的参数都长度或者一些特别标识。总体来说,我们的quicklist会很简单。
看到这里大家基本都可以猜到插入查找删除等操作了

插入

由于咱们的quicklist是一个标准的双向链表,所以他的插入操作的原理会和普通的双向链表插入很像,不过喃由于他使用了其他链表来作为节点。所以喃,我们操作就是找到我们插入的节点。然后进行ziplist的插入就ok了。

/* Optimization levels for size-based filling */
static const size_t optimization_level[] = {4096, 8192, 16384, 32768, 65536};

/* Maximum size in bytes of any multi-element ziplist.
 * Larger values will live in their own isolated ziplists. */
#define SIZE_SAFETY_LIMIT 8192

REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
                                           const int fill, const size_t sz) {
    if (unlikely(!node))
        return 0;

    int ziplist_overhead;
    /* size of previous offset */
    if (sz < 254)    //根据我们ziplist的经验 对prvlen进行编码 小于254一位 大于等于254 五位
        ziplist_overhead = 1;
    else
        ziplist_overhead = 5;

    /* size of forward offset */
    if (sz < 64)        //编码字节的size  根据我们对len的编码
        ziplist_overhead += 1;
    else if (likely(sz < 16384))
        ziplist_overhead += 2;
    else
        ziplist_overhead += 5;

    //不过这个大小只是一个大概的猜想。因为我们知道我们ziplist里面还有很多特殊的操作。这个算出来的是一个最大的大小。总体来说只会比这个小,不会比这个大的
    /* new_sz overestimates if 'sz' encodes to an integer type */
    unsigned int new_sz = node->sz + sz + ziplist_overhead; //计算出一个大概新插入后的大小
    if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
        return 1;
    else if (!sizeMeetsSafetyLimit(new_sz))//判断是不是个安全的大小
        return 0;
    else if ((int)node->count < fill)//这才来判断个数
        return 1;
    else
        return 0;
}

REDIS_STATIC int
_quicklistNodeSizeMeetsOptimizationRequirement(const size_t sz,
                                               const int fill) {
    if (fill >= 0)//大于等于0 是个数
        return 0;

    size_t offset = (-fill) - 1;//小于等于0 级别
    if (offset < (sizeof(optimization_level) / sizeof(*optimization_level))) {
        if (sz <= optimization_level[offset]) {
            return 1;
        } else {
            return 0;
        }
    } else {
        return 0;
    }
}

REDIS_STATIC void __quicklistInsertNode(quicklist *quicklist,
                                        quicklistNode *old_node,
                                        quicklistNode *new_node, int after) {
    if (after) { //插入方式
        new_node->prev = old_node;//设置节点关系
        if (old_node) { //如果原来的节点有咯
            new_node->next = old_node->next;//设置next
            if (old_node->next)
                old_node->next->prev = new_node;
            old_node->next = new_node;
        }
        if (quicklist->tail == old_node)
            quicklist->tail = new_node;
    } else {
        new_node->next = old_node;
        if (old_node) {
            new_node->prev = old_node->prev;
            if (old_node->prev)
                old_node->prev->next = new_node;
            old_node->prev = new_node;
        }
        if (quicklist->head == old_node)
            quicklist->head = new_node;
    }
    /* If this insert creates the only element so far, initialize head/tail. */
    if (quicklist->len == 0) {//第一次加入 设置成头和尾
        quicklist->head = quicklist->tail = new_node;
    }

    if (old_node)//加满了之后进行压缩操作
        quicklistCompress(quicklist, old_node);

    quicklist->len++;
}
/* Add new entry to head node of quicklist.
 *
 * Returns 0 if used existing head.
 * Returns 1 if new head created. */
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
    quicklistNode *orig_head = quicklist->head;//当前的head
    if (likely(
            _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {//看下head让不让你插入
        quicklist->head->zl =
            ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);//让你插入就插
        quicklistNodeUpdateSz(quicklist->head);//更新长度
    } else {
        quicklistNode *node = quicklistCreateNode();//不让插新建一个node
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);//在新的ziplist插入

        quicklistNodeUpdateSz(node);//更新sz
        _quicklistInsertNodeBefore(quicklist, quicklist->head, node);//插入了新节点,维护quicklist的node关系,如果有压缩啥的进行压缩
    }
    quicklist->count++;//cout++
    quicklist->head->count++;//cout++
    return (orig_head != quicklist->head);
}

相对于这种标准的双向链表来说,插入就变得非常简单了。首先是确定插入头或者尾,插入的时候先判断该节点能否插入。能否插入喃是有几个标准的。第一个是不是设置了fill fill为负数的时候代表级别,每个级别ziplist能容纳的大小喃在上面有。如果没有设置的话。就看有没大于安全的标准大小。没有的话再看fill是不是正数,正数代表ziplist能够容纳的个数。这样判断完成能不能够进行该节点的插入。不能的话就创建一个新的节点,把数据插入进去。然后维护一下quicklist的双向链表关系。对老节点看下是否进行压缩操作等。

查找操作

list的查找操作主要是对index的
我们的quicklist的节点是由一个一个的ziplist构成的每个ziplist都有大小。所以我们就只需要先根据我们每个node的个数,从而找到对应的ziplist,调用ziplist的index就能成功找到。

typedef struct quicklistEntry {
    const quicklist *quicklist;//那个quicklist
    quicklistNode *node; //哪一个node
    unsigned char *zi; //找到在ziplist的位置
    unsigned char *value;//str value
    long long longval; //int value
    unsigned int sz; //svalue len
    int offset;//对于ziplist的index
} quicklistEntry;


/* Populate 'entry' with the element at the specified zero-based index
 * where 0 is the head, 1 is the element next to head
 * and so on. Negative integers are used in order to count
 * from the tail, -1 is the last element, -2 the penultimate
 * and so on. If the index is out of range 0 is returned.
 *
 * Returns 1 if element found
 * Returns 0 if element not found */
int quicklistIndex(const quicklist *quicklist, const long long idx,
                   quicklistEntry *entry) {
    quicklistNode *n;
    unsigned long long accum = 0;
    unsigned long long index;
    int forward = idx < 0 ? 0 : 1; /* < 0 -> reverse, 0+ -> forward */ //向前还是像后

    initEntry(entry);
    entry->quicklist = quicklist;//设置list

    if (!forward) {//方向
        index = (-idx) - 1;
        n = quicklist->tail;
    } else {
        index = idx;
        n = quicklist->head;
    }

    if (index >= quicklist->count)//有米有这么多个
        return 0;

    while (likely(n)) { //找到对应的位置
        if ((accum + n->count) > index) {
            break;
        } else {
            D("Skipping over (%p) %u at accum %lld", (void *)n, n->count,
              accum);
            accum += n->count;
            n = forward ? n->next : n->prev;//下一个node
        }
    }

    if (!n)//没找到
        return 0;

    D("Found node: %p at accum %llu, idx %llu, sub+ %llu, sub- %llu", (void *)n,
      accum, index, index - accum, (-index) - 1 + accum);

    entry->node = n;//设置节点
    if (forward) {//
        /* forward = normal head-to-tail offset. */
        entry->offset = index - accum;//位置 即ziplist的index
    } else {
        /* reverse = need negative offset for tail-to-head, so undo
         * the result of the original if (index < 0) above. */
        entry->offset = (-index) - 1 + accum;
    }

    quicklistDecompressNodeForUse(entry->node);//需要解压就解压
    entry->zi = ziplistIndex(entry->node->zl, entry->offset);//找到节点块
    ziplistGet(entry->zi, &entry->value, &entry->sz, &entry->longval);//获取节点的值 反编码
    /* The caller will use our result, so we don't re-compress here.
     * The caller can recompress or delete the node as needed. */
    return 1;
}

删除

/* Delete a range of elements from the quicklist.
 *
 * elements may span across multiple quicklistNodes, so we
 * have to be careful about tracking where we start and end.
 *
 * Returns 1 if entries were deleted, 0 if nothing was deleted. */
int quicklistDelRange(quicklist *quicklist, const long start,
                      const long count) {
    if (count <= 0)
        return 0;

    unsigned long extent = count; /* range is inclusive of start position *///默认你删的个数是对的

    if (start >= 0 && extent > (quicklist->count - start)) {//但是喃没有这么多给你删
        /* if requesting delete more elements than exist, limit to list size. */
        extent = quicklist->count - start;
    } else if (start < 0 && extent > (unsigned long)(-start)) {
        /* else, if at negative offset, limit max size to rest of list. */
        extent = -start; /* c.f. LREM -29 29; just delete until end. */
    }

    quicklistEntry entry;
    if (!quicklistIndex(quicklist, start, &entry))//先找一下位置
        return 0;

    D("Quicklist delete request for start %ld, count %ld, extent: %ld", start,
      count, extent);
    quicklistNode *node = entry.node;//拿到要删的第一个node

    /* iterate over next nodes until everything is deleted. */
    while (extent) {
        quicklistNode *next = node->next;//下一个节点喃 都是往一个方向删除哈

        unsigned long del;
        int delete_entire_node = 0;
        if (entry.offset == 0 && extent >= node->count) {//他是第一个节点 并且要删的节点数大于这个node保存的节点数 就把这个node一起干掉
            /* If we are deleting more than the count of this node, we
             * can just delete the entire node without ziplist math. */
            delete_entire_node = 1;
            del = node->count;
        } else if (entry.offset >= 0 && extent >= node->count) {//要删的个数大于 并且是正向来的
            /* If deleting more nodes after this one, calculate delete based
             * on size of current node. */
            del = node->count - entry.offset;
        } else if (entry.offset < 0) {//如果是反向的话
            /* If offset is negative, we are in the first run of this loop
             * and we are deleting the entire range
             * from this start offset to end of list.  Since the Negative
             * offset is the number of elements until the tail of the list,
             * just use it directly as the deletion count. */
            del = -entry.offset;

            /* If the positive offset is greater than the remaining extent,
             * we only delete the remaining extent, not the entire offset.
             */
            if (del > extent)//看下能删多少个
                del = extent;
        } else {
            /* else, we are deleting less than the extent of this node, so
             * use extent directly. */
            del = extent;
        }

        D("[%ld]: asking to del: %ld because offset: %d; (ENTIRE NODE: %d), "
          "node count: %u",
          extent, del, entry.offset, delete_entire_node, node->count);

        if (delete_entire_node) {//删节点
            __quicklistDelNode(quicklist, node);
        } else {
            quicklistDecompressNodeForUse(node);//解压
            node->zl = ziplistDeleteRange(node->zl, entry.offset, del);//ziplist删range
            quicklistNodeUpdateSz(node);//更新大小
            node->count -= del;//更新个数
            quicklist->count -= del;//更新个数
            quicklistDeleteIfEmpty(quicklist, node);//看下节点是不是空了
            if (node)
                quicklistRecompressOnly(quicklist, node);//压缩回去
        }

        extent -= del;//减除已经删除了的

        node = next;//继续进行删除

        entry.offset = 0;
    }
    return 1;
}

这里给出的是删除一个range
我们的操作也很简单。
1.先把要删除的个数确定下来。可能会存在删除的个数过多,其实没有那么多的情况。
2.找到要开始删除的节点。删除的情况其实特别的就是获取当前的offset为0,要删除的个数大于等于当前节点的数据个数。这样就把它全部删除。否则的话就判断当前节点需要删除几个

总结

quciklist把ziplist进行了封装,保证了ziplist的最大大小。从而压缩了内存。进一步的提高了效率。

magicdeMacBook-Pro:~ magic$ redis-cli
127.0.0.1:6379> rpush xislist 'xistest'
(integer) 2
127.0.0.1:6379> object encoding xislist
"quicklist"
127.0.0.1:6379>

其实我们可以发现。我们的list就是使用的quicklist.
参照方法。我们就可以知道我们的,命令的效率。

你可能感兴趣的:(redis quicklist)