Redis 实战3

系列文章目录

本文将从跳跃表的实现、整数集合来展开

Redis 实战 Ⅲ

  • 系列文章目录
  • 跳跃表的实现
    • 跳跃表节点
  • 前进指针
    • 跨度
  • 整数集合的实现
  • 升级
    • 升级的好处
      • 提升灵活性
      • 节约内存
  • 降级
  • 整数集合 API
  • 总结

跳跃表的实现

Redis 的跳跃表由 redis.h/zskiplistNode 和 redis.h/zskiplist 两个结构定义, 其中 zskiplistNode 结构用于表示跳跃表节点, 而 zskiplist结构则用于保存跳跃表节点的相关信息, 比如节点的数量, 以及指向表头节点和表尾节点的指针, 等等。
在这里插入图片描述
图5-1 展示了一个跳跃表示例, 位于图片最左边的是 zskiplist 结构, 该结构包含以下属性:

header :指向跳跃表的表头节点。
tail :指向跳跃表的表尾节点。
level :记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)。
length :记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)。

位于zskiplist 结构右方的是四个 zskiplistNode 结构, 该结构包含以下属性:

层(level):节点中用 L1L2L3 等字样标记节点的各个层, L1 代表第一层, L2 代表第二层,以此类推。每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离。在上面的图片中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。
后退(backward)指针:节点中用 BW 字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。
分值(score):各个节点中的 1.02.03.0 是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。
成员对象(obj):各个节点中的 o1 、 o2 和 o3 是节点所保存的成员对象。

注意表头节点和其他节点的构造是一样的: 表头节点也有后退指针、分值和成员对象, 不过表头节点的这些属性都不会被用到, 所以图中省略了这些部分, 只显示了表头节点的各个层。

本节接下来的内容将对 zskiplistNode 和 zskiplist 两个结构进行更详细的介绍。

跳跃表节点

跳跃表节点的实现由 redis.h/zskiplistNode 结构定义:

typedef struct zskiplistNode {

    // 后退指针
    struct zskiplistNode *backward;

    // 分值
    double score;

    // 成员对象
    robj *obj;

    // 层
    struct zskiplistLevel {

        // 前进指针
        struct zskiplistNode *forward;

        // 跨度
        unsigned int span;

    } level[];

} zskiplistNode;

跳跃表节点的 level 数组可以包含多个元素, 每个元素都包含一个指向其他节点的指针, 程序可以通过这些层来加快访问其他节点的速度, 一般来说, 层的数量越多, 访问其他节点的速度就越快。

每次创建一个新跳跃表节点的时候, 程序都根据幂次定律 (power law,越大的数出现的概率越小) 随机生成一个介于 1 和 32 之间的值作为 level 数组的大小, 这个大小就是层的“高度”。

图5-2 分别展示了三个高度为 1 层、 3 层和 5 层的节点, 因为 C 语言的数组索引总是从 0 开始的, 所以节点的第一层是 level[0] , 而第二层是 level[1] , 以此类推。
在这里插入图片描述

前进指针

每个层都有一个指向表尾方向的前进指针(level[i].forward 属性), 用于从表头向表尾方向访问节点。

图5-3 用虚线表示出了程序从表头向表尾方向, 遍历跳跃表中所有节点的路径:

1、 迭代程序首先访问跳跃表的第一个节点(表头),然后从第四层的前进指针移动到表中的第二个节点;
2、 在第二个节点时,程序沿着第二层的前进指针移动到表中的第三个节点;
3、 在第三个节点时,程序同样沿着第二层的前进指针移动到表中的第四个节点;
4、 当程序再次沿着第四个节点的前进指针移动时,它碰到一个NULL,程序知道这时已经到达了跳跃表的表尾,于是结束这次遍历;
在这里插入图片描述

跨度

层的跨度(level[i].span 属性)用于记录两个节点之间的距离:

两个节点之间的跨度越大, 它们相距得就越远。
指向 NULL 的所有前进指针的跨度都为 0 , 因为它们没有连向任何节点。
初看上去, 很容易以为跨度和遍历操作有关, 但实际上并不是这样 —— 遍历操作只使用前进指针就可以完成了, 跨度实际上是用来计算排位(rank)的: 在查找某个节点的过程中, 将沿途访问过的所有层的跨度累计起来, 得到的结果就是目标节点在跳跃表中的排位。

举个例子, 图 5-4 用虚线标记了在跳跃表中查找分值为 3.0 、 成员对象为 o3 的节点时, 沿途经历的层: 查找的过程只经过了一个层, 并且层的跨度为 3 , 所以目标节点在跳跃表中的排位为 3 。

整数集合的实现

整数集合(intset)是 Redis 用于保存整数值的集合抽象数据结构, 它可以保存类型为 int16_t 、 int32_t 或者 int64_t 的整数值, 并且保证集合中不会出现重复元素。

每个intset.h/intset 结构表示一个整数集合:

typedef struct intset {

    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;

contents 数组是整数集合的底层实现: 整数集合的每个元素都是 contents 数组的一个数组项(item), 各个项在数组中按值的大小从小到大有序地排列, 并且数组中不包含任何重复项。

length 属性记录了整数集合包含的元素数量, 也即是 contents 数组的长度。

虽然intset 结构将 contents 属性声明为 int8_t 类型的数组, 但实际上 contents 数组并不保存任何 int8_t 类型的值 —— contents 数组的真正类型取决于 encoding 属性的值:

如果 encoding 属性的值为 INTSET_ENC_INT16 , 那么 contents 就是一个 int16_t 类型的数组, 数组里的每个项都是一个 int16_t 类型的整数值 (最小值为 -32,768 ,最大值为 32,767 )。
如果 encoding 属性的值为 INTSET_ENC_INT32 , 那么 contents 就是一个 int32_t 类型的数组, 数组里的每个项都是一个 int32_t 类型的整数值 (最小值为 -2,147,483,648 ,最大值为 2,147,483,647 )。
如果 encoding 属性的值为 INTSET_ENC_INT64 , 那么 contents 就是一个 int64_t 类型的数组, 数组里的每个项都是一个 int64_t 类型的整数值 (最小值为 -9,223,372,036,854,775,808 ,最大值为 9,223,372,036,854,775,807 )。

虽然contents 数组保存的四个整数值中, 只有 -2675256175807981027 是真正需要用 int64_t 类型来保存的, 而其他的 1 、 3 、 5 三个值都可以用 int16_t 类型来保存, 不过根据整数集合的升级规则, 当向一个底层为 int16_t 数组的整数集合添加一个 int64_t 类型的整数值时, 整数集合已有的所有元素都会被转换成 int64_t 类型, 所以 contents 数组保存的四个整数值都是 int64_t 类型的, 不仅仅是-2675256175807981027 。

升级

每当我们要将一个新元素添加到整数集合里面, 并且新元素的类型比整数集合现有所有元素的类型都要长时, 整数集合需要先进行升级(upgrade), 然后才能将新元素添加到整数集合里面。

升级整数集合并添加新元素共分为三步进行:

1、 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间;
2、 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变;
3、 将新元素添加到底层数组里面;
举个例子, 假设现在有一个 INTSET_ENC_INT16 编码的整数集合, 集合中包含三个 int16_t 类型的元素,因为每个元素都占用 16 位空间, 所以整数集合底层数组的大小为 3 * 16 = 48 位,
现在,假设我们要将类型为 int32_t 的整数值 65535 添加到整数集合里面, 因为 65535 的类型 int32_t 比整数集合当前所有元素的类型都要长, 所以在将 65535 添加到整数集合之前, 程序需要先对整数集合进行升级。

升级首先要做的是, 根据新类型的长度, 以及集合元素的数量(包括要添加的新元素在内), 对底层数组进行空间重分配。

整数集合目前有三个元素, 再加上新元素 65535 , 整数集合需要分配四个元素的空间, 因为每个 int32_t 整数值需要占用 32 位空间, 所以在空间重分配之后, 底层数组的大小将是 32 * 4 = 128 位
虽然程序对底层数组进行了空间重分配, 但数组原有的三个元素 1 、 2 、 3 仍然是 int16_t 类型, 这些元素还保存在数组的前 48 位里面, 所以程序接下来要做的就是将这三个元素转换成 int32_t 类型, 并将转换后的元素放置到正确的位上面, 而且在放置元素的过程中, 需要维持底层数组的有序性质不变。

首先,因为元素 3 在 1 、 2 、 3 、 65535 四个元素中排名第三, 所以它将被移动到 contents 数组的索引 2 位置上, 也即是数组 64 位至 95 位的空间内
接着,因为元素 2 在 1 、 2 、 3 、 65535 四个元素中排名第二, 所以它将被移动到 contents 数组的索引 1 位置上, 也即是数组的 32位至 63 位的空间内之后,因为元素 1 在 1 、 2 、 3 、 65535 四个元素中排名第一, 所以它将被移动到 contents 数组的索引 0 位置上, 也即是数组的 0 位至 31 位的空间内然后,因为元素 65535 在 1 、 2 、 3 、 65535 四个元素中排名第四, 所以它将被添加到 contents 数组的索引 3 位置上, 也即是数组的96 位至 127 位的空间内,最后,程序将整数集合 encoding 属性的值从 INTSET_ENC_INT16 改为 INTSET_ENC_INT32 , 并将 length 属性的值从 3 改为 4
因为每次向整数集合添加新元素都可能会引起升级, 而每次升级都需要对底层数组中已有的所有元素进行类型转换, 所以向整数集合添加新元素的时间复杂度为 O(N) 。

其他类型的升级操作, 比如从 INTSET_ENC_INT16 编码升级为 INTSET_ENC_INT64 编码, 或者从 INTSET_ENC_INT32 编码升级为 INTSET_ENC_INT64 编码, 升级的过程都和上面展示的升级过程类似。

升级之后新元素的摆放位置

因为引发升级的新元素的长度总是比整数集合现有所有元素的长度都大, 所以这个新元素的值要么就大于所有现有元素, 要么就小于所有现有元素:

在新元素小于所有现有元素的情况下, 新元素会被放置在底层数组的最开头(索引 0 );
在新元素大于所有现有元素的情况下, 新元素会被放置在底层数组的最末尾(索引 length-1 )。

升级的好处

整数集合的升级策略有两个好处, 一个是提升整数集合的灵活性, 另一个是尽可能地节约内存。

提升灵活性

因为C 语言是静态类型语言, 为了避免类型错误, 我们通常不会将两种不同类型的值放在同一个数据结构里面。

比如说, 我们一般只使用 int16_t 类型的数组来保存 int16_t 类型的值, 只使用 int32_t 类型的数组来保存 int32_t 类型的值, 诸如此类。

但是,因为整数集合可以通过自动升级底层数组来适应新元素, 所以我们可以随意地将 int16_t 、 int32_t 或者 int64_t 类型的整数添加到集合中, 而不必担心出现类型错误, 这种做法非常灵活。

节约内存

当然,要让一个数组可以同时保存 int16_t 、 int32_t 、 int64_t 三种类型的值, 最简单的做法就是直接使用 int64_t 类型的数组作为整数集合的底层实现。 不过这样一来, 即使添加到整数集合里面的都是 int16_t 类型或者 int32_t 类型的值, 数组都需要使用 int64_t 类型的空间去保存它们, 从而出现浪费内存的情况。

而整数集合现在的做法既可以让集合能同时保存三种不同类型的值, 又可以确保升级操作只会在有需要的时候进行, 这可以尽量节省内存。

比如说, 如果我们一直只向整数集合添加 int16_t 类型的值, 那么整数集合的底层实现就会一直是 int16_t 类型的数组, 只有在我们要将int32_t 类型或者 int64_t 类型的值添加到集合时, 程序才会对数组进行升级。

降级

整数集合不支持降级操作, 一旦对数组进行了升级, 编码就会一直保持升级后的状态。

整数集合 API

在这里插入图片描述

总结

1、跳跃表

跳跃表是有序集合的底层实现之一, 除此之外它在 Redis 中没有其他应用。
Redis 的跳跃表实现由 zskiplist 和 zskiplistNode 两个结构组成, 其中 zskiplist 用于保存跳跃表信息(比如表头节点、表尾节点、长度), 而 zskiplistNode 则用于表示跳跃表节点。
每个跳跃表节点的层高都是 132 之间的随机数。
在同一个跳跃表中, 多个节点可以包含相同的分值, 但每个节点的成员对象必须是唯一的。
跳跃表中的节点按照分值大小进行排序, 当分值相同时, 节点按照成员对象的大小进行排序。

2、整数集合

整数集合是集合键的底层实现之一。
整数集合的底层实现为数组, 这个数组以有序、无重复的方式保存集合元素, 在有需要时, 程序会根据新添加元素的类型, 改变这个数组的类型。
升级操作为整数集合带来了操作上的灵活性, 并且尽可能地节约了内存。
整数集合只支持升级操作, 不支持降级操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/606629.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面向初学者:什么是图数据库

当数据成为关键生产要素,许多企业开始面临利用海量数据辅助企业复杂决策的现实难题。而在数据爆发式增长,关联复杂度激增的趋势下,图数据库成为企业加工关联数据、挖掘隐藏价值、智能决策升级的关键技术之一,在全球范围内开始被使…

如何更快地执行 Selenium 测试用例?

前言: 当我们谈论自动化时,首先想到的工具之一是 Selenium。我们都知道Selenium WebDriver 是一个出色的 Web 自动化工具。实施Selenium 自动化测试的主要原因是加速 selenium 测试。在大多数情况下,Selenium 的性能比手动的要好得多。但是&…

(2024,DONN,OCNN,复数域,交替的非线性激活层与振荡器层,复值反向传播)深度振荡神经网络

Deep Oscillatory Neural Network 公和众和号:EDPJ(进 Q 交流群:922230617 或加 VX:CV_EDPJ 进 V 交流群) 目录 0. 摘要 1. 简介 2. 方法 2.1 深度振荡神经网络(DONN) 2.2 振荡卷积神经网…

人物特效游戏玩法,门坎低,适合新手上手项目【揭密】

项目简介: 本项目涉及我们日常使用的美肤产品和效果维持,我们需要提交自己的作品,完成官方网站发布的任务。任务完成后,提交审核,一旦审批通过,收益就会到账。 项 目 地 址 : laoa1.cn/1961.…

Python-VBA函数之旅-round函数

目录 一、round函数的常见应用场景 二、round函数使用注意事项 三、如何用好round函数? 1、round函数: 1-1、Python: 1-2、VBA: 2、推荐阅读: 个人主页: https://blog.csdn.net/ygb_1024?spm1010.2…

部署tomcat部署LNAMT

这里写目录标题 部署tomcatjava环境安装 部署LNAMT更改tomcat端口号 tomcat就是中间件之一,tomcat本身是一个容器,专门用来运行java程序,java语言开发的网页.jsp就应该运行于tomcat中。而tomcat本身的运行也依赖于jdk环境。 部署tomcat java…

LVS 负载均衡部署 NAT模式

一、环境准备 配置环境: 负载调度器:配置双网卡 内网:172.168.1.11(ens33) 外网卡:12.0.0.1(ens37)二台WEB服务器集群池:172.168.1.12、172.168.1.13 一台NFS共享服务器:172.168.1.14客户端&#xff…

2024年全网最新AI实景自动无人直播软件:引领智能直播新潮流;打造智能化、互动性强的直播平台

随着互联网的飞速发展,直播已经成为商家品牌推广和产品宣传的重要方式。然而,AI实景自动无人直播软件的问世,进一步推动了直播行业的智能化进程,为商家带来了全新的直播体验。(ai无人自动直播大量招商加盟;…

【解疑】ZIP分卷压缩文件如何设置和取消密码?

压缩大文件,我们可以设置压缩成ZIP分卷文件,这样更利于传输和存储。如果分卷文件比较重要,还可以设置密码保护,那ZIP分卷压缩文件的密码如何设置和取消呢?下面一起来看看吧! 设置ZIP分卷密码: …

配电室智能巡检机器人

近年来,生产过程高度自动化,各工矿企业关键场所需定期巡检维护。但目前巡检主要靠人工,既耗时费力效率又低,且受环境等因素影响,巡检难以全面规范,隐患或问题易被忽视。在此情况下,如何利用现有…

Python爬虫基础知识学习(以爬取某二手房数据、某博数据与某红薯(书)评论数据为例)

一、爬虫基础流程 爬虫的过程模块化,基本上可以归纳为以下几个步骤: 1、分析网页URL:打开你想要爬取数据的网站,然后寻找真实的页面数据URL地址; 2、请求网页数据:模拟请求网页数据,这里我们介…

安卓模拟器访问主机局域网

误打误撞能够访问主机局域网了 但是不太懂是因为哪一部分成功的 先记录一下 PC:mac系统 安卓编译器:Android Studio 步骤 只需要在PC上进行设置 1. 在【设置】中,打开已连接的Wi-Fi的【详细信息】 2. TCP/IP --> 配置IPv6,修…

roofline model加速模型部署最后一公里

文章目录 模型部署教程来啦:)什么是Roofline Model?算法模型相关指标计算量计算峰值参数量访存量带宽计算密度kernel size对计算密度的影响output size对计算密度的影响channel size对计算密度的影响group convolution对计算密度的影响tensor reshape对计算密度的影…

网站使用SSL证书有什么好处

SSL证书是一种用于加密在网络上传输的数据以确保安全性和隐私的数字证书。下面我们来谈谈一个网站使用SSL证书后有哪些好处: 首先,使用SSL证书可以保护用户的隐私。在没有SSL证书的情况下,用户的个人信息和敏感数据可能会被黑客窃取或篡改。…

npm安装指定版本,npm删除依赖,卸载依赖

安装指定版本 npm中安装指定的版本号,格式为 ‘包名版本号’ npm install 包名称版本号 --save 例如安装jquery: npm install jquery3.0.0 --save在package.json里面可以看到对应的包: "jquery": "^3.0.0"注意:已有…

基于springboot实现医院药品管理系统项目【项目源码+论文说明】

基于springboot实现医院药品管理系统演示 摘要 身处网络时代,随着网络系统体系发展的不断成熟和完善,人们的生活也随之发生了很大的变化,人们在追求较高物质生活的同时,也在想着如何使自身的精神内涵得到提升,而读书就…

CentOS 重启网络失败service network restart

命令 service network restart 提示 Job for network.service failed because the control process exited with error code. See “systemctl status network.service” and “journalctl -xe” for details. 原因分析 使用journalctl -xe命令查看日志后的具体错误 -- Un…

基于springboot实现疾病防控综合系统项目【项目源码+论文说明】

基于springboot实现疾病防控综合系统演示 摘要 在如今社会上,关于信息上面的处理,没有任何一个企业或者个人会忽视,如何让信息急速传递,并且归档储存查询,采用之前的纸张记录模式已经不符合当前使用要求了。所以&…

PDPS15---安装过程---常遇问题---分享

目录 问题1 安装失败 1.1 运行第一步出错 1.2 解决 问题2 路径错误 2.1 错误 2.2 解决 问题3 运行失败 3.1 无法找到路径 3.2 原因分析 3.3 解决 问题4 拒绝访问 4.1 出现提示 4.2 分析 4.3 解决 问题5 许可证过期 5.1 PD找不到许可证 5.2 解决 问题1 安装失败…

hypertherm海宝EDGE控制器显示屏工控机维修

海宝工控机维修V3.0/4.0/5.0;hypertherm数控切割机系统MICRO EDGE系统显示屏维修; 美国hypertherm公司mirco edge数控系统技术标准如下: 1) p4处理器 2) 512mb内存 3) 80g硬盘,1.44m内置软驱…