Redis的列表对象笔记

616 阅读4分钟

列表对象

列表数据类型是Redis里非常常用的类型,当我们想用一个键关联一组对象时就可以用列表数据类型来存储。列表的功能并不复杂,现在就来看看Redis是怎么实现列表功能的。

列表的编码

列表的编码一共有三种:

  • OBJ_ENCODING_ZIPLIST 压缩列表
  • OBJ_ENCODING_QUICKLIST 编码为ziplist的快速列表
  • OBJ_ENCODING_LINKEDLIST 不再使用的旧列表,使用双端链表

其中OBJ_ENCODING_LINKEDLIST编码如下面代码所示,已经被标记为不使用了。

#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */

OBJ_ENCODING_ZIPLIST编码虽然有定义创建函数,但是我下载了源码然后全文搜索也没找到哪里有调用这个函数,也已经不使用了。

robj *createZiplistObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_LIST,zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

OBJ_ENCODING_ZIPLIST

关于ziplist的实现可以查看笔记

OBJ_ENCODING_LINKEDLIST

Redis在版本3.2之前列表的底层编码有OBJ_ENCODING_LINKEDLISTOBJ_ENCODING_ZIPLIST两种,OBJ_ENCODING_LINKEDLIST是在元素比较大且数量比较多的情况下使用的。

/* Node, List, and Iterator are the only data structures used currently. */

typedef struct listNode {
    struct listNode *prev;
    struct listNode *next;
    void *value;
} listNode;

typedef struct list {
    listNode *head;
    listNode *tail;
    void *(*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void *key);
    unsigned long len;
} list;

可以看到OBJ_ENCODING_LINKEDLIST定义了一个listlistNode结构。list结构引用了首尾两个节点,这样就可以快速的在首尾编辑节点。listNode结构引用了前后两个节点,实现了双端链表。

OBJ_ENCODING_QUICKLIST

总共三种编码,有两种编码没有用到,剩下的是就是列表的主角编码OBJ_ENCODING_LINKEDLIST了。quicklist的定义如下:

/* 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; // 存储的元素总和      
    unsigned long len;   // 节点的数量     
    int fill : 16;       // 节点的大小设置   
    unsigned int compress : 16; // 节点压缩深度设置;0=off;1表示quicklist两端各有一个节点不压缩,中间的节点压缩
} quicklist;

看起来和OBJ_ENCODING_LINKEDLISTlist没什么区别,重点在于quicklistNode了。

/* 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: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
    struct quicklistNode *prev; // 前一个节点
    struct quicklistNode *next; // 后一个节点
    unsigned char *zl;          // 节点存储的值指针
    unsigned int sz;            // ziplist的大小
    unsigned int count : 16;    // 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;

可以看到quicklistNode最神秘的就是存储值的结构了,quicklistNode默认使用ziplist实现。下面是quicklist添加元素的解析:

/* 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;
    # 判断旧头节点的ziplist加上新元素后的大小是否超过限制,这个限制取决于quicklist结构的fill字段
    if (likely(
            _quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
        # 没有超过限制就直接将元素加入到旧头节点的ziplist中
        quicklist->head->zl =
            ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
        quicklistNodeUpdateSz(quicklist->head);
    } else {
        # 超过限制就新建一个node
        quicklistNode *node = quicklistCreateNode();
        # 元素加入到新节点的ziplist中
        node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);

        quicklistNodeUpdateSz(node);
        # 并成为新的头节点
        _quicklistInsertNodeBefore(quicklist, quicklist->head, node);
    }
    # 更新元素数量
    quicklist->count++;
    quicklist->head->count++;
    # 返回是否有新节点创建
    return (orig_head != quicklist->head);
}

结论

Redis在版本3.2之前分别在两种情况下使用OBJ_ENCODING_LINKEDLISTOBJ_ENCODING_ZIPLIST两种编码。OBJ_ENCODING_LINKEDLIST操作简单复杂度低但是内存占用高,OBJ_ENCODING_ZIPLIST则正好相反。后面Redis就实现了OBJ_ENCODING_QUICKLIST编码,融合了OBJ_ENCODING_LINKEDLISTOBJ_ENCODING_ZIPLIST两种编码的优势,成为了列表的唯一指定编码。

参考资料

【Redis源码剖析】 - 浅谈Redis内置数据结构之压缩列表ziplist

Redis源码剖析--快速列表quicklist

Redis源码剖析--quicklist