Redis6
案例
127.0.0.1:6379> config get list*
1) "list-compress-depth"
2) "0"
3) "list-max-ziplist-size"
4) "-2"
127.0.0.1:6379> type list1
list
127.0.0.1:6379>
-
ziplist 压缩配置:
list-compress-depth 0
表示一个 quicklist 两端不被压缩的节点个数。这里的节点是指 quicklist 双向链表的节点,而不是指 ziplist 里面的数据项个数,参数
list-compress-depth
的取值含义如下:- 0:是个特殊值,表示都不压缩。这是Redis的默认值。
- 1:表示 quicklist 两端各有 1 个节点不压缩,中间的节点压缩。
- 2:表示 quicklist 两端各有 2 个节点不压缩,中间的节点压缩。
- 3:表示 quicklist 两端各有 3 个节点不压缩,中间的节点压缩。
- 依此类推…
-
ziplist 中 entry 配置:
list-max-ziplist-size -2
当取正值的时候,表示按照数据项个数来限定每个 quicklist 节点上的 ziplist 长度。比如,当这个参数配置成 5 的时候,表示每个 quicklist 节点的 ziplist 最多包含 5 个数据项。当取负值的时候,表示按照占用字节数来限定每个 quicklist 节点上的 ziplist 长度。这时,它只能取 -1 到 -5 这五个值,每个值含义如下:
- -5:每个 quicklist 节点上的 ziplist 大小不能超过 64 Kb。(注:1kb => 1024 bytes)
- -4:每个 quicklist 节点上的 ziplist 大小不能超过 32 Kb。
- -3:每个 quicklist 节点上的 ziplist 大小不能超过 16 Kb。
- -2:每个 quicklist 节点上的 ziplist 大小不能超过 8 Kb。(-2 是 Redis 给出的默认值)
- -1:每个 quicklist 节点上的 ziplist 大小不能超过 4 Kb。
List 的编码格式
list 用 quicklist 来存储,quicklist 存储了一个双向链表,每个节点都是一个 ziplist。
在 Redis3.0 之前,list 采用的底层数据结构是 ziplist 压缩列表和 linkedList 双向链表,在高版本的 Redis 中底层数据结构是 quicklist(替换了 ziplist 和 linkedList),而 quicklist 也用到了 ziplist。
结论:quicklist 就是 【双向链表 + 压缩列表】组合,因为一个 quicklist 就是一个链表,而链表中的每个元素又是一个压缩列表。在较早版本的 Redis 中,list 有两种底层实现:
- 当列表对象中元素的长度比较小或者数量比较少的时候,采用压缩列表 ziplist 来存储
- 当列表对象中元素的长度比较大或者数量比较多的时候,则会转而使用双向列表 linkedlist 来存储
两者各有优缺点:
- ziplist 的优点就是内存紧凑,访问效率高,缺点是更新效率低,并且数据量较大时,可能导致大量的内存复制
- linkedlist 的优点就是节点修改的效率高,但是需要额外的内存开销,并且节点较多时,会产生大量的内存碎片
为了结合两者的优点,在 Redis 3.2 之后,list 的底层实现变为快速列表 quicklist。
quicklist 总纲
quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
源码分析
-
quicklist.h,head 和 tail 指向双向列表的表头和表尾
-
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: 0 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. * 'bookmakrs are an optional feature that is used by realloc this struct, * so that they don't consume memory when not used. */ 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 : QL_FILL_BITS; /* fill factor for individual nodes */ unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */ unsigned int bookmark_count: QL_BM_BITS; quicklistBookmark bookmarks[]; } quicklist;
-
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 temporary 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 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;
-
-
quicklistNode 中的 *zl 指向一个 ziplist,一个 ziplist 可以存放多个元素
Redis7
案例
127.0.0.1:6379> config get list*
1) "list-compress-depth"
2) "0"
3) "list-max-ziplist-size"
4) "-2"
5) "list-max-listpack-size"
6) "-2"
127.0.0.1:6379>
listpack 紧凑列表,是用来替代 ziplist 的新版数据结构,在 7.0 版本已经没有 ziplist 的配置了(6.0 版本仅部分数据类型作为过渡阶段在使用)。
源码说明
lpush
命令执行后直接调用 pushGenericCommand
函数
/* Implements LPUSH/RPUSH/LPUSHX/RPUSHX.
* 'xx': push if key exists. */
void pushGenericCommand(client *c, int where, int xx) {
int j;
robj *lobj = lookupKeyWrite(c->db, c->argv[1]);
if (checkType(c,lobj,OBJ_LIST)) return;
if (!lobj) {
if (xx) {
addReply(c, shared.czero);
return;
}
lobj = createQuicklistObject();
quicklistSetOptions(lobj->ptr, server.list_max_listpack_size,
server.list_compress_depth); // ⭐️ 读出 size 深度
dbAdd(c->db,c->argv[1],lobj);
}
for (j = 2; j < c->argc; j++) {
listTypePush(lobj,c->argv[j],where);
server.dirty++;
}
addReplyLongLong(c, listTypeLength(lobj));
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}
/* LPUSH <key> <element> [<element> ...] */
void lpushCommand(client *c) {
pushGenericCommand(c,LIST_HEAD,0);
}
void pushGenericCommand(client *c, int where) {
int j, pushed = 0;
for (j = 2; j < c->argc; j++) {
if (sdslen(c->argv[j]->ptr) > LIST_MAX_ITEM_SIZE) {
addReplyError(c, "Element too large");
return;
}
}
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
if (lobj && lobj->type != OBJ_LIST) {
addReply(c,shared.wrongtypeerr);
return;
}
for (j = 2; j < c->argc; j++) {
if (!lobj) {
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++;
}
addReplyLongLong(c, (lobj ? listTypeLength(lobj) : 0));
if (pushed) {
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_LIST,event,c->argv[1],c->db->id);
}
server.dirty += pushed;
}
void lpushCommand(client *c) {
pushGenericCommand(c,LIST_HEAD);
}
robj *createQuicklistObject(void) {
quicklist *l = quicklistCreate();
robj *o = createObject(OBJ_LIST,l);
o->encoding = OBJ_ENCODING_QUICKLIST;
return o;
}
List 的编码格式
- list 用 quicklist 来存储,quicklist 存储了一个双向链表,每个节点都是一个 listpack
- quicklist 是 listpack 和 linkedlist 的结合体