列表类型 作为 Redis 最重要的数据类型之一,广泛使用在 各种列表业务、消息队列 等场景
实际前面我们一直没有提到一种 数据结构 快速列表 quicklist
,就是为了等这一刻,因为 快速列表 是 列表类型的专属 数据结构,而且 其实现 基础
为 压缩列表 ziplist 和 双端链表 ……
快速列表quicklist 结构
首先我们分析一下 双端链表 和 列表 这两种数据结构的情况
- 双端链表 在优点在于 头尾增删数据的 复杂度都是 O(1) 的;但是 列表类型 并不只是 lpush、lpop、rpush、rpop 这些操作,其中有很多 lindex、linsert 命令,这些的操作都是 O(n),而且 双端链表 的每个节点都是独立的内存块,地址不连续,节点多了就容易产生 内存碎片
- 压缩列表 的优点在于 内存是连续的,存储效率很高;但是不利于修改效率太低,经常会引发内存 realloc,而且甚至引起连锁更新就是 O(n2)
于是这个时候 快速列表就出现了,集合 双端链表 和 压缩列表 两种数据结构 的优势,如果对 这两种数据结构还不太熟悉的可以参考 双端链表、压缩列表
通过上图我们看到又两个 数据结构 组成的 快速列表结构,但是 很快我们就发现了一个问题,每个 链表节点 存放多少个 压缩列表节点 合适呢,比如 列表类型数据中共有 20个元素,我们可以有很多种存储方案,20x1、10x2 、5x4、4x5、2x10、1x20
- 每个链表节点下的 压缩列表 元素越少,那么内存碎片就越多,从而降低存储效率。极端情况是每个链表节点下 压缩列表 只包含一个元素,这就是一个普通的双端链表
- 每个链表节点下的压缩列表元素越多,那么 压缩列表 分配大块连续内存空间的难度就越大,有可能有的时候内存里有相当多小块的空闲空间,这些小块空间加起来很大,但却找不到一块足够大的空闲空间分配给压缩列表的情况,同样降低存储效率。极端情况是整个 快速列表 只有一个节点,所有的元素都分配在这独有的一个节点的 压缩链表 里面,这就是一个普通的压缩列表了
在这种纠结的、需要平衡的情况下,Redis 提供了一个配置参数 list-max-ziplist-size
,对应 server 设置是 server.list_max_ziplist_size
,默认值 -2
- 如果配置 >0,代表限定的 每个链表节点上 存储的 最大 压缩列表 长度
- 如果配置 <0,则是根据占用的字节数来限定 每个链表节点上 压缩列表长度
-5
每个链表节点上 的 压缩列表大小 <= 64KB-4
每个链表节点上 的 压缩列表大小 <= 32KB-3
每个链表节点上 的 压缩列表大小 <= 16KB-2
每个链表节点上 的 压缩列表大小 <= 8KB-1
每个链表节点上 的 压缩列表大小 <= 4KB
而 列表类型 还有一个特性,就是 开头和结尾 的节点数据往往是最容易被访问的,所以针对这种 场景,Redis 提供了另外一个配置参数 list-compress-depth
,对应 server 配置是 server.list_compress_depth
,默认值 0
,能把中间的 节点 通过 LZF (无损压缩算法)
进行压缩,大量节省空间,各种参数对应含义如下:
0
是个特殊值,表示都不压缩1
表示 快速列表两端各有 1个节点 不压缩,中间的节点压缩2
表示 快速列表两端各有 2个节点 不压缩,中间的节点压缩3
表示 快速列表两端各有 3个节点 不压缩,中间的节点压缩依此
类推…
数据结构代码如下:
下面我们就 Redis 列表类型 一些典型的 API 进行解析~~~
源码解析
由于有些方法里的代码量比较大,我们这里按照 典型的代码片段进行解析,同志们可以根据文章提示的代码位置 和 代码里面的关键词 在源码中搜素,可能数据结构一些元素 看不太懂什么意思,没关系,先混个脸熟,后面看完回头再看过来就明白了
快速列表 及其 节点的 创建函数为 quicklistCreate()
和 quicklistCreateNode()
,逻辑比较简单,主要为 属性的初始化
列表类型中 用的最多的就是 头尾插入,但是 他们使用的处理函数都是 quicklistPush()
,然后再根据位置分别调用 头插函数quicklistPushHead()
、尾插函数quicklistPushTail()
- 如果插入节点中的 ziplist 大小没有超过限制( list-max-ziplist-size ),那么直接调用 ziplistPush 函数压入
- 如果插入节点中的 ziplist 大小超过了限制,则新建一个 quicklist 节点(自然会创建一个新的 ziplist ),新的数据项会压入到新的 ziplist,新的 quicklist 节点插入到原有的 quicklist 上
不过这里还有另外一个 函数 likely()
,属于 系统优化函数,是 linux 提供给程序员的编译优化方法,目的是将 ‘分支转移’ 的信息提供给编译器,这样编译器可以对代码进行优化,以减少指令跳转带来的性能下降
|
|
列表另外一个常用的操作就是 POP,执行函数为 quicklistPop()
,而具体逻辑处理函数为 quicklistPopCustom()
本文作者: wettper
本文链接: http://www.web-lovers.com/redis-source-list.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!