QuickList快速链表
Redis在3.2版本引入了新的数据结构QuickList,它是一个双端链表,可看成LinkedList + ZipList,只不过LinkList链表中的每个节点都是一个ZipList。
知识点:
-
问题1:为什么存在了ZipList,还需要QuickList的存在?
ZipList虽然节省内存,但申请内存必须是连续空间,如果内存占用较多,申请内存效率很低。所以我们必须限制ZipList的长度和entry大小。 -
问题2:但是我们要存储大量数据,超出了ZipList最佳的上限该怎么办?
我们可以创建多个ZipList来分片存储数据。 -
问题3:数据拆分后比较分散,不方便管理和查找,这多个ZipList如何建立联系?
Redis在3.2版本引入了新的数据结构QuickList,它是一个双端链表,只不过链表中的每个节点都是一个ZipList。
QuickList的结构示意图如下:
可以看出,quickList的本质就是双端链表,只不过链表中的节点是ziplist,通过这种形式,将分片的ziplist联系起来。
QuickList配置项
- 为了避免QuickList中的每个ZipList中entry过多,Redis提供了一个配置项:
list-max-ziplist-size
来限制。
-
如果值为正,则代表ZipList的允许的entry个数的最大值
-
如果值为负,则代表zipList的最大内存大小,分5种情况:默认值为
-2
-
-1:每个ZipList的内存占用不能超过4kb
-
-2:每个ZipList的内存占用不能超过8kb
-
-3:每个ZipList的内存占用不能超过16kb
-
-4:每个ZipList的内存占用不能超过32kb
-
-5:每个ZipList的内存占用不能超过64kb
-
- 除了控制ZipList的大小,QuickList还可以对节点的ZipList做压缩。通过配置项
list-compress-depth
来控制。因为链表般都是从首尾访问较多,所以首尾是不压缩的。这个参数是控制首尾不压缩的节点个数:-
0:特殊值,代表不压缩
-
1:标示QuickList的首尾各有1个节点不压缩,中间节点压缩
-
2:标示QuickList的首尾各有2个节点不压缩,中间节点压缩
-
以此类推
-
QuickList数据结构
typedef struct quicklist {
// quicklist头结点
quicklistNode *head;
// quicklist 尾节点
quicklistNode *tail;
// 所有的ziplist中的entry总数量
unsigned long count; /* total count of all entries in all ziplists */
// ziplist节点数量
unsigned long len; /* number of quicklistNodes */
// ziplist中entry的上限,默认为-2,和redis中配置的 list-max-ziplist-size 一致
int fill : QL_FILL_BITS; /* fill factor for individual nodes */
// 首尾节点不压缩的个数,若设置为1,则首尾节点各有一节点不压缩;设置为0,则代表所有节点不压缩
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;
typedef struct quicklistNode {
// 前一个节点(ziplist)指针
struct quicklistNode *prev;
// 后一个节点(ziplist)指针
struct quicklistNode *next;
// 当前节点ziplist指针
unsigned char *zl;
// 当前节点ziplist的字节大小,即zlbytes
unsigned int sz; /* ziplist size in bytes */
// 当前节点ziplist中entry的数量
unsigned int count : 16; /* count of items in ziplist */
// 编码方式:1-ziplist; 2-lzf压缩模式
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
// 数据容器类型:1-其他(预留扩展类型);2-ziplist
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
// 是否被压缩:1-说明被解压,将来要重新压缩。
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;
从上图可看出:
quicklist的每个节点为quickListNode,实际上内容是zipList。
由于compress=1
,代表首尾各存在一节点不压缩,中间节点压缩,中间节点压缩为0x4…等乱码,读的时候需要解压缩;写的时候需要重新压缩。
总结
QuickList的特点:——兼具链表和Ziplist的优点
-
是一个节点为ZipList的双端链表
-
节点采用ZipList,解决了传统链表的内存占用问题
-
控制了ZipList大小,解决连续内存空间申请效率问题
-
中间节点可以压缩,进一步节省内存