Redis数据结构-QuickList
在《Reids数据结构》这篇文章中,我们说到了Redis的
List
数据类型底层的数据结构是使用的quicklist
,本文简单说下quicklist
。
Redis在版本7.0的时候已经将quicklist
的节点从ziplist
换成了listpack
,关于ziplist
以及listpack
可以看看这篇文章:《Redis数据结构-ZipList与ListPack》。本文主要以quicklist
的节点是ziplist
来说明。
首先quicklist
是一个双向链表结构,像是ziplist
以及linkedlist
的集合体,每个节点就是一个个ziplist
,然后每个节点使用双向指针连接起来。
结构设计
在源码quicklist.h第105行可以看到quicklist
的结构是这样定义的:
/* quicklist是一个 40字节的结构体(在 64 位系统上),用于描述 quicklist。
* 'count' 是所有节点中的总entry数。
* 'len' 是quicklist节点的数目。
* 'compress' 是一个数字,表示不压缩的 quicklist 节点数(如果压缩功能被禁用,则为 0)。
* 'fill' 是用户请求(或默认)的填充因子。
* 'bookmarks' 是一个可选特性,用于重新分配此结构体时使用,以避免不使用时占用内存。*/
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* 所有 ziplist 中的entry数 */
unsigned long len; /* quicklistNode 的数量 */
int fill : QL_FILL_BITS; /* 每个节点的填充因子 */
unsigned int compress : QL_COMP_BITS; /* 深度不压缩的末尾节点;0=禁用 */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;
*head
:表示指向头节点的指针。*tail
:表示指向尾节点的指针。count
: 所有ziplist
中的entry
数量总和。len
:quicklist
中quicklistNode
的个数。fill
:每个节点的填充因子,存放list-max-ziplist-size
配置项的值。compress
: 每个节点的压缩深度设置,存放list-compress-depth
配置项的值。bookmarks
:是一个可选特性,用于重新分配此结构体时使用,以避免不使用时占用内存。
quicklistNode的结构
quicklistNode
的结构同样在源码quicklist.h中:
/* 我们使用bit filed(位域)将quicklistNode限制在32字节。
* count:16 位,最大值为 65536(max zl bytes 为 65k,因此实际最大值 < 32k)。
* encoding:2 位,RAW=1、LZF=2。
* container:2 位,NONE=1、ZIPLIST=2。
* recompress:1 位,bool,如果节点临时解压缩以供使用,则为 true。
* attempted_compress:1 位,boolean,用于测试时验证。
* extra:10 位,用于未来的自由使用;填充剩余的 32 位 */
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 或 LZF==2 */
unsigned int container : 2; /* NONE==1 或 ZIPLIST==2 */
unsigned int recompress : 1; /* 是否对节点进行了临时解压缩 */
unsigned int attempted_compress : 1; /* 节点无法压缩;太小 */
unsigned int extra : 10; /* 更多位用于未来使用 */
} quicklistNode
-
*prev
:表示指向前一个节点的指针。 -
*next
:表示指向后一个节点的指针。 -
*zl
:数据指针。如果当前节点的数据没有压缩,那么它指向一个ziplist
结构;否则,它指向一个quicklistLZF
结构。 -
sz
:表示*zl
指向的ziplist
大小(包括zlbytes
,zltail
,zllen
,zlend
和各个数据项),单位是字节,。注意:如果
ziplist
被压缩了,sz
的值依然还是压缩之前的ziplist
的大小。 -
count
:表示ziplist
中entry
节点数量。只有16bit
。 -
encoding
:表示ziplist
是否被压缩以及使用的什么压缩算法,1代表没有压缩,2代表使用的LZF压缩算法压缩的。 -
container
:预留字段,原本是用来表示quicklist
节点是直接存数据还是使用ziplist
(或者其他数据结构),但是Redis6.0的实现里,这个值固定为2。 -
recompress
:表示是否对节点进行了临时解压缩,比如当我们使用类似lindex
这样的命令查看了某一项本来压缩的数据时,需要把数据暂时解压,这个时候就设置recompress=1
做一个标记,等有机会再把数据重新压缩。
上面说到*zl
还可能指向一个quicklistLZF
结构,这个结构的源码在quicklist.h第64行:
/* quicklistLZF 是一个 4+N 字节的结构体,其中包含了 'sz' 和 'compressed' 两个字段。
* 'sz' 表示 'compressed' 字段的字节长度。
* 'compressed' 是一个 LZF 压缩过的数据,它的总长度为 'sz' 字节。
* 注意:quicklistNode->zl 压缩后的长度存储在 quicklistLZF->sz 中。
* 当 quicklistNode->zl 被压缩时,node->zl 指向 quicklistLZF */
typedef struct quicklistLZF {
unsigned int sz; /* LZF 大小,以字节为单位 */
char compressed[];
} quicklistLZF;
sz
:表示compressed
字段的字节长度。compressed
:是一个LZF压缩过的数据,它的总长度为sz
个字节。