Redis内部数据结构quicklist详解
在本文中,我们介绍一个Redis内部数据结构——quicklist。Redis对外暴露的list数据类型,它底层实现所依赖的内部数据结构就是quicklist。
我们在讨论中还会涉及到两个Redis配置(在redis.conf中的ADVANCED CONFIG部分):
list-max-ziplist-size -2
list-compress-depth 0
我们在讨论中会详细解释这两个配置的含义。
注:本文讨论的quicklist实现基于Redis源码的3.2分支。
quicklist概述
Redis对外暴露的上层list数据类型,经常被用作队列使用。比如它支持的如下一些操作:
lpush: 在左侧(即列表头部)插入数据。
rpop: 在右侧(即列表尾部)删除数据。
rpush: 在右侧(即列表尾部)插入数据。
lpop: 在左侧(即列表头部)删除数据。
这些操作都是O(1)时间复杂度的。
一、基本结构#
quicklist 实际上是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。
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;
prev: 指向链表前一个节点的指针。
next: 指向链表后一个节点的指针。
zl: 数据指针。如果当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。
sz: 表示zl指向的ziplist的总大小(包括zlbytes, zltail, zllen, zlend和各个数据项)。需要注意的是:如果ziplist被压缩了,那么这个sz的值仍然是压缩前的ziplist大小。
count: 表示ziplist里面包含的数据项个数。这个字段只有16bit。稍后我们会一起计算一下这16bit是否够用。
encoding: 表示ziplist是否压缩了(以及用了哪个压缩算法)。目前只有两种取值:2表示被压缩了(而且用的是LZF压缩算法),1表示没有压缩。
container: 是一个预留字段。本来设计是用来表明一个quicklist节点下面是直接存数据,还是使用ziplist存数据,或者用其它的结构来存数据(用作一个数据容器,所以叫container)。但是,在目前的实现中,这个值是一个固定的值2,表示使用ziplist作为数据容器。
recompress: 当我们使用类似lindex这样的命令查看了某一项本来压缩的数据时,需要把数据暂时解压,这时就设置recompress=1做一个标记,等有机会再把数据重新压缩。
attempted_compress: 这个值只对Redis的自动化测试程序有用。我们不用管它。
extra: 其它扩展字段。目前Redis的实现里也没用上。
Copy
typedef struct quicklistLZF { unsigned int sz; /* LZF size in bytes*/ char compressed[]; } quicklistLZF; quicklistLZF结构表示一个被压缩过的ziplist
其中:
sz: 表示压缩后的ziplist大小。
compressed: 是个柔性数组(flexible array member),存放压缩后的ziplist字节数组。
Copy
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;
head: 指向头节点(左侧第一个节点)的指针。
tail: 指向尾节点(右侧第一个节点)的指针。
count: 所有ziplist数据项的个数总和。
len: quicklist节点的个数。
fill: 16bit,ziplist大小设置,存放list-max-ziplist-size参数的值。
compress: 16bit,节点压缩深度设置,存放list-compress-depth参数的值。
二、常用操作
2.1 插入#
quicklist可以选择在头部或者尾部进行插入(quicklistPushHead和quicklistPushTail),而不管是在头部还是尾部插入数据,都包含两种情况:
如果头节点(或尾节点)上ziplist大小没有超过限制(即_quicklistNodeAllowInsert返回1),那么新数据被直接插入到ziplist中(调用ziplistPush)。
如果头节点(或尾节点)上ziplist太大了,那么新创建一个quicklistNode节点(对应地也会新创建一个ziplist),然后把这个新创建的节点插入到quicklist双向链表中。
也可以从任意指定的位置插入。quicklistInsertAfter和quicklistInsertBefore就是分别在指定位置后面和前面插入数据项。这种在任意指定位置插入数据的操作,要比在头部和尾部的进行插入要复杂一些。
当插入位置所在的ziplist大小没有超过限制时,直接插入到ziplist中就好了;
当插入位置所在的ziplist大小超过了限制,但插入的位置位于ziplist两端,并且相邻的quicklist链表节点的ziplist大小没有超过限制,那么就转而插入到相邻的那个quicklist链表节点的ziplist中;
当插入位置所在的ziplist大小超过了限制,但插入的位置位于ziplist两端,并且相邻的quicklist链表节点的ziplist大小也超过限制,这时需要新创建一个quicklist链表节点插入。
对于插入位置所在的ziplist大小超过了限制的其它情况(主要对应于在ziplist中间插入数据的情况),则需要把当前ziplist分裂为两个节点,然后再其中一个节点上插入数据。
2.2 查找#
list的查找操作主要是对index的我们的quicklist的节点是由一个一个的ziplist构成的每个ziplist都有大小。所以我们就只需要先根据我们每个node的个数,从而找到对应的ziplist,调用ziplist的index就能成功找到。
2.3 删除#
区间元素删除的函数是 quicklistDelRange
quicklist 在区间删除时,会先找到 start 所在的 quicklistNode,计算删除的元素是否小于要删除的 count,如果不满足删除的个数,则会移动至下一个 quicklistNode 继续删除,依次循环直到删除完成为止。
quicklistDelRange 函数的返回值为 int 类型,当返回 1 时表示成功的删除了指定区间的元素,返回 0 时表示没有删除任何元素。
2.4 其它#
除了上面介绍的基本操作之外还有一些其它操作,大家可以尝试着根据链表和压缩列表的数据结构来分析一些quicklist这些操作的时间复杂度。
操作 时间复杂度
quicklistCreate:创建 quicklist ?
quicklistInsertAfter:在某个元素的后面添加数据 ?
quicklistInsertBefore:在某个元素的前面添加数据 ?
quicklistReplaceAtIndex:替换某个元素 ?
quicklistDelEntry:删除单个元素 ?
quicklistDelRange:删除区间元素 ?
quicklistPushHead:头部插入元素 ?
quicklistPushTail:尾部插入元素 ?
小结
Redis quicklist是Redis 3.2版本以后针对链表和压缩列表进行改造的一种数据结构,是 zipList 和 linkedList 的混合体,相对于链表它压缩了内存。进一步的提高了效率。
- Redis内部数据结构详解——skiplist
- Redis内部数据结构详解之跳跃表(skiplist)
- Redis内部数据结构总结(4)quicklist
- Redis中的强大的数据结构跳跃表(skiplist)的内部详解及实际运用
- Redis内部数据结构详解之跳跃表(skiplist)
- Redis内部数据结构详解之双向链表(linkedlist)
- Redis内部数据结构详解(1)——dict
- Redis数据结构和内部编码--列表(list)
- Redis内部数据结构详解之简单动态字符串(sds)
- Redis内部数据结构详解之字典(dict)
- redis 五种数据结构详解(string,list,set,zset,hash)
- 【Redis】redis 五种数据结构详解(string,list,set,zset,hash)
- Redis内部数据结构详解(2)——sds
- Redis内部数据结构详解(2)--sds
- Redis 内部数据结构详解(1):dict
- Redis内部数据结构详解(1)——dict
- Redis内部数据结构详解之简单动态字符串(sds)
- Redis数据结构详解之List(二)
- Redis内部数据结构详解(3)——robj
- Redis内部数据结构详解之整数集合(intset)