在`Redis 3.2`版本之后,为了进一步提升`Redis`的性能,统一采用`quicklist`来存储列表对象。`quicklist`存储了一个双向列表,每个列表的节点是一个`ziplist`,所以实际上`quicklist`并不是一个新的数据结构,它就是`linkedlist`和`ziplist`的结合,然后被命名为快速列表。
#### quicklist 内部存储结构
`quicklist`中每一个节点都是一个`quicklistNode`对象,其数据结构定义如下:
~~~c
typedef struct quicklistNode {
struct quicklistNode *prev;//前一个节点
struct quicklistNode *next;//后一个节点
unsigned char *zl;//当前指向的ziplist或者quicklistLZF
unsigned int sz;//当前ziplist占用字节
unsigned int count : 16;//ziplist中存储的元素个数,16字节(最大65535个)
unsigned int encoding : 2; //是否采用了LZF压缩算法压缩节点 1:RAW 2:LZF
unsigned int container : 2; //存储结构,NONE=1, ZIPLIST=2
unsigned int recompress : 1; //当前ziplist是否需要再次压缩(如果前面被解压过则为true,表示需要再次被压缩)
unsigned int attempted_compress : 1;//测试用
unsigned int extra : 10; //后期留用
} quicklistNode;
~~~
然后各个`quicklistNode`就构成了一个快速列表`quicklist`:
~~~c
typedef struct quicklist {
quicklistNode *head;//列表头节点
quicklistNode *tail;//列表尾节点
unsigned long count;//ziplist中一共存储了多少元素,即:每一个quicklistNode内的count相加
unsigned long len; //双向链表的长度,即quicklistNode的数量
int fill : 16;//填充因子
unsigned int compress : 16;//压缩深度 0-不压缩
} quicklist;
~~~
根据这两个结构,我们可以得到`Redis 3.2`版本之后的列表对象的一个存储结构示意图:
![](https://img.kancloud.cn/61/2b/612be20dad662dc04bf9b73f1a93a7ea_1139x438.png)
#### quicklist 的 compress 属性
`compress`是用来表示压缩深度,`ziplist`除了内存空间是连续之外,还可以采用特定的`LZF`压缩算法来将节点进行压缩存储,从而更进一步的节省空间,压缩深度可以通过参数`list-compress-depth`控制:
* 0:不压缩(默认值)
* 1:首尾第 1 个元素不压缩
* 2:首位前 2 个元素不压缩
* 3:首尾前 3 个元素不压缩
* 以此类推
注意:之所以采取这种压缩两端节点的方式是因为很多场景都是两端的元素访问率最高的,而中间元素访问率相对较低。所以在实际使用时,我们可以根据自己的实际情况选择是否进行压缩,以及具体的压缩深度。
#### quicklistNode 的 zl 指针
`zl`指针默认指向了`ziplist`,上面提到`quicklistNode`中有一个`sz`属性记录了当前`ziplist`占用的字节,不过这仅仅限于当前节点没有被压缩(通过`LZF`压缩算法)的情况,如果当前节点被压缩了,那么被压缩节点的`zl`指针会指向另一个对象`quicklistLZF`,而不会直接指向`ziplist`。`quicklistLZF`是一个`4+N`字节的结构:
~~~c
typedef struct quicklistLZF {
unsigned int sz;// LZF大小,占用4字节
char compressed[];//被压缩的内容,占用N字节
} quicklistLZF;
~~~
#### quicklist 对比原始两种编码的改进
`quicklist`同样采用了`linkedlist`的双端列表特性,然后`quicklist`中的每个节点又是一个`ziplist`,所以`quicklist`就是综合平衡考虑了`linkedlist`容易产生空间碎片的问题和`ziplist`的读写性能两个维度而设计出来的一种数据结构。使用`quicklist`需要注意以下两点:
* 如果`ziplist`中的`entry`个数过少,最极端情况就是只有`1`个`entry`的压缩列表,那么此时`quicklist`就相当于退化成了一个普通的`linkedlist`。
* 如果`ziplist`中的`entry`过多,那么也会导致一次性需要申请的内存空间过大(`ziplist`空间是连续的),而且因为`ziplist`本身就是以时间换空间,所以过多`entry`也会影响到列表对象的读写性能。
`ziplist`中的`entry`个数可以通过参数`list-max-ziplist-size`来控制:
~~~c
list-max-ziplist-size 1
~~~
注意:这个参数可以配置正数也可以配置负数。正数表示限制每个节点中的`entry`数量,如果是负数则只能为`-1~-5`,其代表的含义如下:
* \-1:每个`ziplist`最多只能为`4KB`
* \-2:每个`ziplist`最多只能为`8KB`
* \-3:每个`ziplist`最多只能为`16KB`
* \-4:每个`ziplist`最多只能为`32KB`
* \-5:每个`ziplist`最多只能为`64KB`
- Redis 为什么这么快
- 什么是 Redis
- Redis 的安装
- Redis 到底有多快
- Redis 是单线程还是多线程
- Redis 为什么选择使用单线程来执行请求
- 什么是 IO 多路复用机制
- Redis 中 I/O 多路复用的应用
- 一个简单的字符串,为什么 Redis 要设计的如此特别
- Redis 的 9 种数据类型
- 二进制安全字符串
- sds 空间分配策略
- sds 和 C 语言字符串区别
- sds 是如何被存储的
- type 属性
- encoding 属性
- 通过牺牲速度来节省内存,Redis 是觉得自己太快了吗
- 什么是压缩列表
- ziplist 的存储结构
- entry 存储结构
- ziplist 数据示例
- ziplist 连锁更新问题
- 为了加快速度,Redis 都做了哪些“变态”设计
- 列表对象
- linkedlist
- linkedlist 和 ziplist 的选择
- quicklist
- 列表对象常用操作命令
- Redis 中哈希分布不均匀该怎么办
- 哈希对象
- hashtable
- ziplist
- ziplist 和 hashtable 的编码转换
- 哈希对象常用命令
- 同一份数据,Redis 为什么要存”两次”
- 五种基本类型之集合对象
- intset 编码
- 集合对象常用命令
- 五种基本类型之有序集合对象
- skiplist 编码
- ziplist 编码
- ziplist 和 skiplist 编码转换
- 有序集合对象常用命令
- 要想用活 Redis,Lua 脚本是绕不过去的坎
- 发布与订阅
- 基于频道的实现
- 基于模式的实现
- Lua 脚本
- Lua 脚本的调用
- Lua 脚本中执行 Redis 命令
- Lua 脚本摘要
- Lua 脚本文件
- 脚本异常
- 作为一款内存数据库,为什么断电后 Redis 数据不会丢失
- Redis 持久化机制
- RDB 持久化机制
- AOF 持久化机制
- 内存耗尽后 Redis 会发生什么
- 内存回收
- 过期策略
- 8 种淘汰策略
- LRU 算法
- LFU 算法
- 不能回滚的 Redis 事务还能用吗
- Redis 有事务吗
- Redis 事务实现原理
- Redis 事务 ACID 特性
- watch 命令
- watch 命令的作用
- watch 原理分析
- Redis 为什么不直接用 master-slave 集群
- Redis 集群方案
- 主从复制
- 配置一主两从 master-slave 集群
- 主从复制原理分析
- 主从服务的不足之处
- Sentinel(哨兵)机制为什么从神坛滑落
- 哨兵 Sentinel 机制
- Sentinel 原理分析
- 配置 Sentinel 集群
- Sentinel 机制实战
- Sentinel 机制的不足之处
- Redis Cluster 集群凭什么成为了最终的胜利者
- Redis 分布式集群方案
- 客户端实现分片
- 中间代理服务实现分片
- Redis Cluster 方案
- 手动配置一个 Redis Cluster 集群
- Redis Cluster 集群常用命令
- 客户端如何使用 Redis Cluster 集群
- Redis Cluster 的不足
- 如何从 10 亿数据中快速判断是否存在某一个元素
- 缓存雪崩
- 缓存击穿
- 缓存穿透
- 布隆过滤器(Bloom Filter)
- 布隆过滤器的 2 大特点
- 布隆过滤器的实现(Guava)
- 布隆过滤器的如何删除