Redis List

在Redis3.2版之前,Redis使用压缩列表和双向链表作为List的底层实现。当元素个数比较少并且元素长度比较小时,Redis使用压缩列表实现,否则Redis使用双向链表实现。

ziplist存在问题

  1. 不能保存过多的元素,否则查找复杂度高,性能降低。

  2. 由于每个节点保存了前一个节点的长度,不同长度使用的字节数不一样,所以在更新节点的时候有可能引起长度的变化导致连锁更新问题。

为了解决上面两个问题,在Redis3.2版之后,引入了quicklist。

quicklist

quicklist可以理解为是ziplist和链表的结合体,一个quicklist是一个双向链表,链表中的每一个节点是一个ziplist。

quicklist结构定义

typedef struct quicklist {
// 头指针
quicklistNode *head;
// 尾指针
quicklistNode *tail;
unsigned long count; /* 列表中的元素总个数,也就是所有ziplist中包含的元素数量之和 */
unsigned long len; /* 链表中节点的个数 */
int fill : QL_FILL_BITS; /* 表示ziplist的大小 */
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;
  • head:指向头结点的指针

  • tail:指向尾节点的指针

  • count:列表中的元素总个数,等于所有节点的ziplist中包含的元素数量之和

  • len:quicklist中quicklistNode节点的个数

  • fill:用来限制quicklistNode中ziplist的大小,为正数时代表ziplist中最多能包含的元素个数,为负数时有以下几种情况:

    数值 含义
    -1 表示ziplist的字节数不能超过4KB
    -2 表示ziplist的字节数不能超过8KB
    -3 表示ziplist的字节数不能超过16KB
    -4 表示ziplist的字节数不能超过32KB
    -5 表示ziplist的字节数不能超过64KB

​除此之外,也可以通过list-max-ziplist-size参数配置最大的字节数。

quicklistNode结构定义

typedef struct quicklistNode {
// 前一个节点
struct quicklistNode *prev;
// 下一个节点
struct quicklistNode *next;
// 指向ziplist压缩列表的指针
unsigned char *zl;
unsigned int sz; /* ziplist压缩列表的字节数 */
unsigned int count : 16; /* ziplist压缩列表的元素个数 */
unsigned int encoding : 2; /* 编码格式:RAW==1 or LZF==2 */
unsigned int container : 2; /* NONE==1 or ZIPLIST==2 */
unsigned int recompress : 1; /* 是否被压缩 */
unsigned int attempted_compress : 1; /* 是否可以被压缩 */
unsigned int extra : 10; /* 预留bit位*/
} quicklistNode;

quicklist创建

quicklist *quicklistCreate(void) {
struct quicklist *quicklist;
// 分配空间
quicklist = zmalloc(sizeof(*quicklist));
// 初始化头尾节点
quicklist->head = quicklist->tail = NULL;
quicklist->len = 0;
quicklist->count = 0;
quicklist->compress = 0;
// 默认为-2,表示ziplist的字节数最大不能超过8KB
quicklist->fill = -2;
quicklist->bookmark_count = 0;
return quicklist;
}

添加元素

添加元素的时候可以在链表的头部或者尾部进行添加,以头部添加为例:

  1. 首先调用_quicklistNodeAllowInsert方法判断是否允许添加元素到ziplist,如果允许,调用ziplistPush方法进行添加
  2. 如果_quicklistNodeAllowInsert不允许添加元素,则需要新创建一个quicklistNode,然后将元素添加到新创建的quicklistNode的压缩列表中
// 从头部添加元素
int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
quicklistNode *orig_head = quicklist->head;
// 判断是否允许添加
if (likely(
_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
// 将元素添加到ziplit
quicklist->head->zl =
ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(quicklist->head);
} else {
// 新创建quicklistNode节点
quicklistNode *node = quicklistCreateNode();
// 添加元素
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD); quicklistNodeUpdateSz(node);
_quicklistInsertNodeBefore(quicklist, quicklist->head, node);
}
// 更新数量
quicklist->count++;
quicklist->head->count++;
return (orig_head != quicklist->head);
}

_quicklistNodeAllowInsert

_quicklistNodeAllowInsert方法用于判断是否允许在某个quicklistNode指向的压缩列表中添加元素。

在quicklist的结构体定义中,fill指定了ziplist中能包含的最大元素个数或者ziplist最大的字节数,_quicklistNodeAllowInsert方法就是判断ziplist中的元素个数或者ziplist的字节数是否超过了限制:

// node:当前的quicklistNode节点
// fill:ziplist中能包含的最大元素个数或者ziplist最大的字节数
// sz:要添加元素的大小
REDIS_STATIC int _quicklistNodeAllowInsert(const quicklistNode *node,
const int fill, const size_t sz) {
if (unlikely(!node))
return 0; int ziplist_overhead;
/* 判断要添加元素的大小是否小于254 */
if (sz < 254)
ziplist_overhead = 1;
else
ziplist_overhead = 5; /* 判断要添加元素的大小是否小于64 */
if (sz < 64)
ziplist_overhead += 1;
else if (likely(sz < 16384))
ziplist_overhead += 2;
else
ziplist_overhead += 5; /* 计算添加元素后的当前的quicklistNode的大小 + 新加入元素的大小 + 插入元素后ziplit的prevlen占用大小 */
unsigned int new_sz = node->sz + sz + ziplist_overhead;
// 判断添加元素后的ziplist的字节数是否超过了fill中设置的大小
if (likely(_quicklistNodeSizeMeetsOptimizationRequirement(new_sz, fill)))
return 1;
else if (!sizeMeetsSafetyLimit(new_sz))
return 0;
else if ((int)node->count < fill) // 判断ziplist的元素个数是否超过了fill设置的大小
return 1;
else
return 0;
}

总结

  1. 在Redis3.2版之前,Redis使用压缩列表和双向链表作为List的底层实现。当元素个数比较少并且元素长度比较小时,Redis使用压缩列表实现,否则Redis使用双向链表实现。

  2. 为了解决压缩列表在节点多的时候查找效率低的问题以及连锁更新问题,在Redis3.2版之后引入了quicklist,quicklist是一个双向链表,链表中的每一个节点是一个ziplist。

  3. quicklist中限定了ziplist的大小,如果超过了限制的大小,新加入元素的时候会生成一个新的quicklistNode节点。

  4. quicklist通过限定ziplist的大小来保证一个ziplist中的元素个数不会太多,如果需要连锁更新,也只在某个quicklistNode节点指向的ziplist中更新,不会引发整个链表的更新,以此来解决压缩列表存在的问题。

参考

陈雷《Redis5设计与源码分析》

极客时间 - Redis源码剖析与实战(蒋德钧)

Redis版本:redis-6.2.5

【Redis】quicklist的更多相关文章

  1. 【Redis】使用Redis Sentinel实现Redis HA

    阅读目录 1 sentinel down-after-milliseconds mymaster 30000 sentinel failover-timeout mymaster 18000 sent ...

  2. 【Redis】编译错误zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory

    [Redis]编译错误zmalloc.h:50:31: fatal error: jemalloc/jemalloc.h: No such file or directory 在安装redis进行编译 ...

  3. 【Redis】Redis cluster集群搭建

    Redis集群基本介绍 Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施installation. Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行 ...

  4. 【Redis】命令学习笔记——列表(list)+集合(set)+有序集合(sorted set)(17+15+20个超全字典版)

    本篇基于redis 4.0.11版本,学习列表(list)和集合(set)和有序集合(sorted set)相关命令. 列表按照插入顺序排序,可重复,可以添加一个元素到列表的头部(左边)或者尾部(右边 ...

  5. 【Redis】命令学习笔记——哈希(hash)(15个超全字典版)

    本篇基于redis 4.0.11版本,学习哈希(hash)相关命令. hash 是一个string类型的field和value的映射表,特别适合用于存储对象. 序号 命令 描述 实例 返回 HSET ...

  6. 【Redis】命令学习笔记——字符串(String)(23个超全字典版)

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). 本篇基于redis 4.0.11版本,学习字符串( ...

  7. 【Redis】命令学习笔记——键(key)(20个超全字典版)

    安装完redis和redis-desktop-manager后,开始学习命令啦!本篇基于redis 4.0.11版本,从对键(key)开始挖坑! 准备工作,使用db1(默认db0,由于之前练习用db0 ...

  8. 【Redis】Linux下Redis安装与redis-desktop-manager使用(无法连接Redis服务器解决方法)

    新手小白开始学习nosql数据库Redis,首先从安装入手. 全文分两部分:Linux下安装redis,可视化工具redis desktop manager的简单使用. 一.安装 下载,解压缩和编译R ...

  9. 【docker】【redis】2.docker上设置redis集群---Redis Cluster部署【集群服务】【解决在docker中redis启动后,状态为Restarting,日志报错:Configured to not listen anywhere, exiting.问题】【Waiting for the cluster to join...问题】

    参考地址:https://www.cnblogs.com/zhoujinyi/p/6477133.html https://www.cnblogs.com/cxbhakim/p/9151720.htm ...

随机推荐

  1. 第一阶段:Java基础之数组

    注意点: @Java语言是把数组当作一个"对象"来看待的 @把数组分为两部分看,一部分是数组的引用,放置在栈内存中,一部分是数组对象,放置在堆内存中 @数组的引用可以指向任何有效的 ...

  2. npm 和 Yarn 镜像站配置

    Node.js 作为近年来非常受欢迎的 Web 开发运行环境,由于开发者众多,贡献开源代码的人也很多,所有这些凝结成了 npm 这个世界上最大的软件包仓库,但是受限于 npm 软件包的服务器在国外,国 ...

  3. DirectX11 With Windows SDK--38 级联阴影映射(CSM)

    前言 在31章我们曾经实现过阴影映射,但是受到阴影贴图精度的限制,只能在场景中相当有限的范围内投射阴影.本章我们将以微软提供的例子和博客作为切入点,学习如何解决阴影中出现的Atrifacts: 边缘闪 ...

  4. Cocos Creator绕远做圆周运动,且变换运动物体的角度

    需求:绕远做圆周运动 并且精灵的角度要随着位置的改变而改变 网上有很多做圆周运动的代码,但是要不然就是角度不变 要不然就是cocos版本老旧 摘了一段3.x的圆周运动,自己加了角度变换 圆周运动,已知 ...

  5. Vue_基础功能循环、计算、绑定、事件处理、组件

    1 <!DOCTYPE html> 2 <html lang="en" xmlns:v-bind="http://www.w3.org/1999/xht ...

  6. 浅谈 TCP、IP、DNS 和 HTTP 的关系

    一.浅谈三个协议的基本概念 1.IP 协议 按层次分,IP网际协议位于网络层,几乎所有的网络的系统都会用到 IP 协议,其重要性非同一般.IP 协议作用就是把各种数据包传送给对方,对方的地址就要看其 ...

  7. 【前端干货】别再羡慕别人的Excel啦,教你点击按钮直接打开侧边栏!

    负责技术支持的葡萄又来啦. 三日不见,我们的客户又为我们发来新的问题. 这次我们需要实现的场景是在前端表格环境中,像模板按钮那样,点击之后弹出一个侧边栏,然后通过点击不同的单元格显示不同的内容. 挤接 ...

  8. FreeRTOS --(14)队列管理之概述

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107221175 在任何的 OS 中,都需要支持任务与任务,中断与任务之间的数据传输机制 ...

  9. 宝藏发现之API接口高效协作神器Apifox

    概述 背景 Apifox官方地址 https://www.apifox.cn/ 前面文章我们已经围绕微服务展开,缺少一个关键前置流程,那就是API接口设计,而在API接口设计开始前本篇先推荐一个非常好 ...

  10. 【转】WinForm窗体刻度尺

    `using System; using System.Drawing; using System.Windows.Forms; using System.Drawing.Drawing2D; nam ...