Redis 快速链表相关源码阅读笔记,源码文件 quicklist.h
& quicklist.c
。
1. 小结
quicklist
是由 ziplist
组成的双向链表,兼顾节省内存空间和提高查询效率的需求。
- 每个
quicklistNode
中包含一个ziplist
,此ziplist
的长度受到 list-max-ziplist-size 的限制 - 考虑到链表的读写操作通常集中于两端,故
quicklist
中间部分的quicklistNode
中的ziplist
可能会使用 LZF 算法进行压缩以进一步节省内存空间,压缩程度由 list-compress-depth 控制
2. quicklist 相关数据结构
quicklist
结构定义如下:
|
|
-
quicklistNode
: 快速链表节点,包含一ziplist
,其结构介绍将在下文详述 -
count
: 该快速链表中所有ziplist
的zlentry
总数 -
len
: 该快速链表中quicklistNode
个数 -
fill
: 其用于限制该快速链表中每个quicklistNode
中ziplist
的长度- 从 ziplist 那一章节中我们知道
ziplist
每次更改操作耗时和其长度成正比,因此ziplist
不应过长,但是如果其过短的话就无法达到节省内存空间的效果,特别地,当fill = 1
时,quicklist
退化成普通的双向链表 - 可在
redis.conf
中通过设置list-max-ziplist-size
进行配置-
其值为正数时,表示的是每个
ziplist
中zlentry
个数(zllen
)上限 -
其值为负数时,表示的是每个
ziplist
所用字节总数(zlbytes
)上限1 2 3 4 5
-5: max size: 64 Kb <-- not recommended for normal workloads -4: max size: 32 Kb <-- not recommended -3: max size: 16 Kb <-- probably not recommended -2: max size: 8 Kb <-- good -1: max size: 4 Kb <-- good
-
- 从 ziplist 那一章节中我们知道
-
compress
: 链表的读写通常情况下集中在两端,因此在quicklist
中,可能会对中间的quicklistNode
节点使用无损数据压缩算法 LZF 对其包含的ziplist
进行压缩以进一步节省内存使用。compress
的大小含义如下:- $0$: 表示不进行压缩
- $\mathbb{Z}^+$: 表示首尾
compress
个quicklistNode
不会被压缩
- 可以看出,
head
和tail
节点永远不会被压缩
quicklistBookmark
quicklist
中最后一个 field 为一个柔性数组成员 bookmarks
,bookmark_count
保存该数组的长度。当 quicklist
长度特别长,需要迭代遍历时,会使用到该数组作为缓存。该数组的长度应保持在较小值,以供高效查找更新,其结构定义如下:
|
|
quicklistNode
quicklistNode
包装了单个 ziplist
,其结构定义如下:
|
|
-
sz
:ziplist
所占用的字节总数(zlbytes
) -
count
: 16-bits(0 to 65535),表示在ziplist
中zlentry
个数(zllen
)。- 我们知道
ziplist
的长度受到quicklist
中fill
参数的限制,在 32 位机器上QL_FILL_BITS = 14
,在 64 位机器上QL_FILL_BITS = 16
- 当
fill > 0
时,表示zllen
,其最大值为 32767,小于 65535 - 当
fill < 0
时,表示zlbytes
,其最大值为 64kb,从 ziplist 中我们知道,当所有zlentry
存储的是 0 到 12 的整数时,每个zlentry
的大小为 $sizeof(prevlen) + sizeof(encoding) = 1 + 1 = 2$ 字节,$64 \times 1024 \div 2 = 32768 < 65535$
- 当
- 综上可知,16-bit 的
count
足够使用
- 我们知道
-
encoding
: 表示所包含的ziplist
是否被压缩,值为 1 表示未被压缩,值为 2 表示已使用 LZF 压缩算法压缩 -
container
: 表示quicklistNode
所包装的数据类型,目前值固定为 2,表示包装的数据类型为ziplist
-
recompress
: 当我们访问ziplist
时,需要解压数据,该参数表示ziplist
是否被临时解压 -
attempted_compress
: 测试用
quicklistLZF
当 quicklistNode
中的 ziplist
被压缩后,quicklistNode->zl
指向 quicklistLZF
结构,其定义如下:
|
|
值得注意的是,此处 sz
指的是压缩后得到的 compressed
字节数组的长度,而 quicklistNode->sz
指的是 ziplist
压缩前的长度。
quicklistIter
quicklist
的迭代器,其结构如下:
|
|
quicklist
: 当前迭代的快速链表current
: 当前迭代的quicklistNode
zi
: 当前迭代的zlentry
offset
: 当前访问的zlentry
在current->zl
中的偏移量direction
: 迭代方向
quicklistEntry
用于描述 quicklist
中的每个 zlentry
的状态。
|
|
quicklist
: 指向该zlentry
所在的快速链表node
: 指向该zlentry
所在的quicklistNode
zi
: 指向该zlentry
offset
: 该zlentry
在node->zl
中的偏移量- 当
zlentry
的数据类型为字符串时,value
和sz
保存了zlentry
的值 - 当
zlentry
的数据类型为整型时,longval
保存了zlentry
的值