quicklist vs ziplist

在一次使用redis list的时候,使用object encoding命令查看编码类型。时出现了quicklist

192.168.99.100:6379> lpush list a b c d
(integer) 4
192.168.99.100:6379> object encoding list
"quicklist"
192.168.99.100:6379>

而我只记得linkedlistziplist。 这次抽了个时间来翻翻源码,为了弄清楚:

list对象的linkedlist编码、ziplist编码、以及编码转换机制是否还存在?

List

redis/src/t_list.c(3.0)

# 使用LPUSH命令,lpushCommand会被调用
void lpushCommand(redisClient *c) {
    pushGenericCommand(c,REDIS_HEAD);
}

void pushGenericCommand(redisClient *c, int where) {
    int j, waiting = 0, pushed = 0;
    # 如果命令是 LPUSH key value1 value2 
    # 则这里的argv = ["LPUSH", "key", "value1", "value2"]
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]); 

    # 如果key对应的类型不是list会报错并退出
    if (lobj && lobj->type != REDIS_LIST) {
        addReply(c,shared.wrongtypeerr);
        return;
    }

    # 每一次循环就是在处理LPUSH操作的一个值
    for (j = 2; j < c->argc; j++) {
        # 尝试将argv[j]转换成合适的编码:RAW、EMBSTR、INT
        c->argv[j] = tryObjectEncoding(c->argv[j]);
        # list的自动创建,创建的list默认使用Ziplist编码
        if (!lobj) {
            lobj = createZiplistObject();
            dbAdd(c->db,c->argv[1],lobj);
        }
        # Push的操作在listTypePush
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }
    # 命令回复
    addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
    if (pushed) {
        char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
        # 发送信号:key已修改
        signalModifiedKey(c->db,c->argv[1]);
        # 发送键空间、键时间通知
        notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
    }
    server.dirty += pushed;
}

void listTypePush(robj *subject, robj *value, int where) {
    # 判断value是否满足编码转换条件
    listTypeTryConversion(subject,value);

    # 判断list是否满足编码转换条件
    # 如果list长度大于等于server.list_max_ziplist_entries
    # 那么list会从Ziplist转换成Linkedlist编码
    if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
        ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
   
    # 针对不同编码,调用不同API来增加list节点
    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
        int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
        value = getDecodedObject(value);
        subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
        decrRefCount(value);
    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
        if (where == REDIS_HEAD) {
            listAddNodeHead(subject->ptr,value);
        } else {
            listAddNodeTail(subject->ptr,value);
        }
        incrRefCount(value);
    } else {
        redisPanic("Unknown list encoding");
    }
}

# 如果添加的value是sds(Simple Dynamic String), sds可以是raw或embstr编码
# 且sds的长度大于server.list_max_ziplist_value
# 那么list会从Ziplist转换成Linkedlist编码
void listTypeTryConversion(robj *subject, robj *value) {
    if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
    if (sdsEncodedObject(value) &&
        sdslen(value->ptr) > server.list_max_ziplist_value)
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}

这是我所熟知的list编码转换机制,而quicklist是redis 3.2新加入的结构。那我直接去看了redis 4.0的版本,了解下list编码转换机制是否有所改变。

redis/src/t_list.c(4.0)

void pushGenericCommand(client *c, int where) {
    ...
    for (j = 2; j < c->argc; j++) {
        if (!lobj) {
            # list自动创建时,使用的是quicklist
            lobj = createQuicklistObject();
            quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
                                server.list_compress_depth);
            dbAdd(c->db,c->argv[1],lobj);
        }
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }
    ...
}

void listTypePush(robj *subject, robj *value, int where) {
    # 添加元素时,如果不是quicklist编码就会报错
    # 那很大程度上意味着list只有一种编码了
    if (subject->encoding == OBJ_ENCODING_QUICKLIST) {
        int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
        value = getDecodedObject(value); # 确保value是一个字符串对象,而不是INT
        size_t len = sdslen(value->ptr);
        # 直接调用的quicklist API
        quicklistPush(subject->ptr, value->ptr, len, pos);
        decrRefCount(value);  # 引用计数器
    } else {
        serverPanic("Unknown list encoding");
    }
}

3.2版本的listTypePush会根据不同的编码调用不同数据结构的API,而4.0直接调用了quicklist的API,以前的编码转换的机制已经不存在了。

Quicklist

这次的目的已达到,但还是来了解下quicklist
quicklist - A doubly linked list of ziplists

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;

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;

typedef struct quicklistEntry {
    const quicklist *quicklist;
    quicklistNode *node;
    unsigned char *zi;
    unsigned char *value;
    long long longval;
    unsigned int sz;
    int offset;
} quicklistEntry;
  • quicklist 是一个双向链表,head、tail分别指向头尾节点
  • quicklistNode 是双向链表的节点,prev、next分别指向前驱、后继结点
  • quicklistNode.zl 指向一个ziplist(或者quicklistLZF结构)
  • quicklistEntry 包裹着list的每一个值,作为ziplist的一个节点

可以想象得到,当一个空的quicklist加入一个值value时,会有以下操作(不一定以这个顺序):

  1. 使用Entry包裹value
  2. 创建一个ziplist,把Entry加入到ziplist中
  3. 创建一个Node,Node.zl指向ziplist
  4. 创建quicklist,将Node加入quicklist中

原来版本的list直接使用的一个ziplist,而现在版本的list可以看成使用多个ziplist,然后通过双向链表连接起来。如果知道ziplist的特性,那这样的优点就很明显了。

由于ziplist的连锁更新。ziplist的插入删除操作在最坏情况下的复杂度为O(n^2),虽然导致整条ziplist连锁更新的概率很低。将原来的ziplist划分成多条ziplist后,插入删除一个元素,即使情况再极端也只会引起一段ziplist的连锁更新。

你可能感兴趣的:(quicklist vs ziplist)