Redis基础数据结构——有序集合

2023-11-18

Redis基础数据结构——有序集合

 redis的有序集合zset类似于Java的SoretedSet和HashMap的结合体,一方面它是一个set,可以保证内部value的唯一性,另一方面它可以给每个value赋予一个score,代表这个score的排序权重。
在这里插入图片描述
 zset可以用来存储学生的成绩,value值是学生的ID,score是学生的考试成绩,可以通过对成绩按分数进行排名得到学生名词。还可以用来存储粉丝列表,value值是粉丝的用户ID,score是关注时间,可以对粉丝列表按关注时间进行排序。

1.redis有序集合的基本操作

zadd key [NX|XX] [CH] [INCR] score member [score member …] #向有zset添加成员及其分数,若已存在,则进行更改

zcard key #获取zset中的成员数量

zcount key min max #获取分数范围内的成员数量

zrange key start stop [WITHSCORES] #返回zset中坐标start到stop之间的成员,注意zset中顺序是按score排列的,不是插入顺序

zrevrange key start stop [WITHSCORES] #返回zset中坐标start到stop之间的成员,此时zset中顺序是按score逆序排列的

在这里插入图片描述

zscore key member #获取指定成员的分数

zrank key member #获取成员在zset中的排名

zrem key member [member …] #删除成员

在这里插入图片描述
2.zset的内部实现

 redis的zset是一个复合结构,一方面它需要一个hash结构来存储value和score的对应关系,另一方面需要提供按照score排序的功能,还需要能够指定score的范围来获取value列表的功能,需求不单一必然导致它的结构是一个复杂结构。
 zset的内部实现是一个字典hash+跳跃列表skiplist,字典在之前的字典结构已经分析过了Redis基础数据结构——字典,现在重点看跳跃列表的结构。
在这里插入图片描述
3.跳跃列表的内部实现

struct zslnode {
	string value;
	double score;
	zslnode*[] forwards;		//多层前向指针
	zslnode* backward;			//后向指针
}

struct zsl {
	zslnode* header;			//跳跃列表头指针
	int maxLevel;				//跳跃列表当前的最高层
	map<string, zslnode*> ht;	//hash结构的所有键值对
}

在这里插入图片描述
 图所示是跳跃列表的示意图,这里只画了四层。Redis的跳跃列表共有64层,可以容纳2^64个元素。每一个块对应结构体代码中的zslnode,haed块的结构也是一样的,只是value值为NULL,score为Double.MIN_VALUE,用以垫底。
 zslnode之间使用指针串起来形成双向链表结构,它们是从小到大有序排列的。不同的zslnode的层高可能不同,层数越高的zslnode越少,同一层zslnode使用指针串起来,每层元素遍历都是从header出发的。
 显然,跳跃列表是一种层级结构,一个元素可以处于多个层级中,在操作过程中可以快速在不同层级之间进行跳跃,因而被称为跳跃列表。

4.跳跃列表的操作

(1)查找过程
在这里插入图片描述
 图所示为查找value3,对应score为3的元素,也就是查找蓝色块的元素的步骤。
 1.首先从此时header的最高层L3开始遍历,试图找到这一层最后一个比目标元素分数小的元素,遍历到的第一个元素为6,比3大,说明L3这一层无法找到3,停止遍历L3层,搜索路径中将3号位置的节点置为header。转入下一层开始遍历。
 2.从header的L2层开始遍历,遍历到的第一个元素为4,比3大,说明L2这一层无法找到3,停止遍历L2层,搜索路径中将2号位置的节点置为header,转入下一层开始遍历。
 3.从header的L1层开始遍历,遍历到的第一个元素为2,比3小,标记它。之后继续往前走,遍历到的第二个元素为4,比3大,说明这一层找不到3了,停止遍历L1层。搜索路径中将1号位置的节点置为刚才标记的value2,标记的元素为2,则从2的下一层开始找。
 4.从2的L0层开始遍历,遇到的第一个元素即为3,找到了value3。搜索路径中将0号位置的节点置为value3。

 将中间经过的一系列节点称为“搜索路径”,它是一个元素节点列表,存放了从最高层到最底层每一层最后一个小于目标元素的元素。上述过程的搜索路径为[value3, value2, header, header]
 有了搜索路径之后,就可以插入新节点了。对于每一个新插入的节点,都需要调用一个随机算法来给它分配一个合理的层数。
 具体的查找过程代码见后面的插入过程。

//新节点随机层数
int zslRandomLevel(void) {
	int level = 1;
	while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
		level += 1;
	return (level < ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

 redis标准源码中的晋升率为25%,即代码中的ZSKIPLIST_P的值,也就是被分配到上一层的概率是这一层的50%。跳跃列表会记录下当前的最高层数ZSKIPLIST_MAXLEVEL,遍历时从这个maxlevel开始遍历。

(2)插入过程

//跳跃列表插入节点
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
	//存储搜索路径
	zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
	//存储经过的节点跨度
	unsigned int rank[ZSKIPLIST_MAXLEVEL];
	int i, level;
	
	serverAssert(!isnan(score));
	x = zsl->header;
	//逐步遍历寻找目标节点,得到“搜索路径”,搜索路径从后往前写入
	for (i = zsl->level-1; i >= 0; i--){
		//存储到达插入位置经过的层号
		rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
		//找到这一层中score小于目标元素的最后一个元素
		while (x->level[i].forward &&
				(x->level[i].forward->score < score ||
					//如果score相等,需要比较value
					(x->level[i].forward->score == score &&
					sdscmp(x->level[i].forward->ele, ele) < 0))) {
			rank[i] += x->level[i].span;
			x = x->level[i].forward;
		}
		/*将每一层遇到的小于目标元素的最后一个元素节点置入搜索路径
		 *对应层的位置,若是该层未遇到这样的结点,则将header填入*/
		update[i] = x;
	} 
	/*插入过程*/
	//随机分配一个层数
	level = zslRandomLevel();
	/*若是随机分配的层数高于当前跳跃列表的最大高度,更新一下跳跃列表的最
	 *大高度*/
	if (level > zsl->level) {
		for (i = zsl->level; i < level; i++) {
			rank[i] = 0;
			update[i] = zsl->header;
			update[i]->level[i].span = zsl->length;
		}
		//更新跳跃列表的层高
		zsl->level = level;
	}
	//创建新节点
	x = zslCreateNode(level, score, ele);
	/*插入新节点,重置插入节点每一层的前向指针,以及每一层搜索路径中
	 *节点的前向指针 */
	for (i = 0; i < level; i++) {
		x->level[i].forward = update[i]->level[i].forward;
		update[i]->level[i].forward = x;

		//用update[i]覆盖更新跨度
		x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
		update[i]->level[i].span = (rank[0] - rank[i]) + 1;
	}
	//还没有到达的层数的跨度也需要增加
	for (i = level; i < zsl->level; i++) {
		update[i]->level[i].span++;
	}
	//重新排列后向指针,后向指针只存在于最底层
	x->backward = (update[0] == zsl->header) ? NULL : update[0];
	if (x->level[0].forward)
		x->level[0].forward->backward = x;
	//插入的节点最后一个节点的情况
	else
		zsl->tail = x;
	//跳跃列表的元素个数+1
	zsl->length++;
	return x;
}

 由代码可见,插入过程分为如下四步:
 1.从当前最高层逐逐步降层寻找目标节点,得到搜索路径,搜索路径由后向前填写。
 2.分配随机层数,若是得到的随机层数高于目前的最高层数,需要将搜索路径的内容扩充至该随机层数,并将当前最高层数更新为该随机层数
 3.创建新节点
 4.将新节点插入,插入过程类似于双向链表的插入,只不过跳跃列表的插入要对每一层的前向指针都要重置。

 注意上面的代码中涉及很多span的修改,span又是什么呢?
 redis的zset为了支持获取元素的排名rank,在skiplist的forward指针上进行了优化,给每个forward指针增加了span属性,span是“跨度”的意思,表示从前一个节点沿着当前层的forward指针跳到当前这个节点中间会跳过多少个节点。

struct zslforward {
	zslnode* item;
	long span;				//跨度
}

struct zsl {
	String value;
	double score;
	zslforward*[] forwards;	//多层前向指针
	zslnode* backward;		//后向指针
}

 这样当要计算一个元素的排名时,只需要将搜索路径列表中存放的所有节点的跨度值进行叠加就可以算出元素的最终rank值。

(2)删除过程
 删除过程与插入过程类型,都是先得到搜索路径,然后处理下指针进行删除即可。

(3)更新过程

//当score改变时,删除这个元素,然后将它重新插入
if (score != curscore) {
	zskiplistNode *node;
	serverAssert(zslDetete(zs->zsl, curscore, ele, &node));
	znode = zslInsert(zs->zsl, score, node->ele);
	node->ele = NULL;
	zslFreeNode(node);
	dictGetVal(de) = &znode->score;
}
return 1;

 进行更新时,若是更新后的score不会改变排序的话,则只需要进行一次查找即可。若是更新后的score会改变排序的话,需要对位置进行重排,很麻烦。
 由代码可见,redis使用的更新策略是先删除这个元素,然后再将这个元素插入进来即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis基础数据结构——有序集合 的相关文章

  • C++ std::set 更新很乏味:我无法就地更改元素

    我发现更新操作std set乏味 因为没有这样的 API参考参数 http en cppreference com w cpp container set 所以我目前所做的是这样的 find element in set by iterat
  • 计算字符串中的常见字符 Python

    该代码的输出仍然是 4 但是 输出应该是 3 存在集合交集 因为我相信这是答案的关键 答案是 4 而不是 3 的原因来自于 s1 中与 s2 匹配的 2 个 qs 和 1 个 r 的数量 s2 qsrqq s1 qqtrr counts1
  • 使用 for..of 迭代时删除 Set 中的元素是否安全?

    是否指定可以删除实例中的任意元素Set迭代时使用for of然后 你不会在一个元素上迭代多次 除了删除的元素之外 您不会错过迭代开始时集合中的任何其他元素 Yes 在迭代集合时添加元素和删除元素是完全可以的 JavaScript 2015
  • Java中如何设置背景图片?

    我正在使用 Java 使用 BlueJ 作为 IDE 开发一个简单的平台游戏 现在 我在游戏中使用多边形和简单形状绘制了玩家 敌人精灵 平台和其他物品 最终我希望用实际图像替换它们 现在我想知道将图像 URL 或来自本地源 设置为游戏窗口
  • WSL Redis 遇到系统尚未使用 systemd 作为 init 系统(PID 1)启动。无法操作[已关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试遵循本文中讨论的 Redis 安装过程article https www digitalocean com community
  • 是否有可嵌入的 Java 替代 Redis?

    根据这个线程 https stackoverflow com questions 3047010 best redis library for java 如果我想从Java中使用Redis Jedis是最好的选择 然而 我想知道是否有任何库
  • Docker-compose Predis 不通过 PHP 连接

    我正在尝试使用 docker compose 将 PHP 与 redis 连接 docker compose yml version 2 services redis image redis 3 2 2 php image company
  • 非不相交集并集的最佳算法是什么?

    假设有两个 非不相交 点集 笛卡尔空间 执行这两个集的并集的最佳情况复杂度算法是什么 由于点坐标是任意的 并且它们之间没有特殊关系 因此我不认为这个问题是一个几何特定问题 这是有效地将 S1 和 S2 合并成新集合 S 的通用问题 我知道有
  • Redis hash写入速度非常慢

    我面临一个非常奇怪的问题 使用 Redis 时 我的写入速度非常糟糕 在理想的情况下 写入速度应该接近 RAM 上的写入速度 这是我的基准 package redisbenchmark import redis clients jedis
  • Python 集合上的迭代顺序

    我正在解析两个大文件 GB 大小顺序 每个文件包含keys以及对应的values Some keys在两个文件之间共享 但对应的不同values 对于每个文件 我想写入一个新文件keys 以及对应的values with keys 代表 f
  • Spring Data Redis JedisConnectionException:流意外结束

    雷迪斯3 0 5Spring数据Redis 1 3 6绝地武士2 6 3 我们的 Web 应用程序通过 pub sub 从 Redis 接收数据 还以键 值对的形式在 Redis 上执行数据读 写 读 写发生在监听线程 独立监控线程和htt
  • php PDO 可以获取两个结果集吗?如果是,1 个结果集和 1 个以上结果集哪个更好?

    如果可能的话 如何获取两个结果集 sth dbh gt prepare SELECT FROM tb1 WHERE cond1 SELECT from tb2 Where cond2 sth gt execute row sth gt fe
  • 通过 StackExchange.Redis 连接到 Redis Servier

    我尝试使用以下方法制作一个测试项目Redis https redis io服务器 通过 Virtual Box 安装在 Linux Ubuntu 虚拟机上 Linux 机器通过 Virtual Box 的桥接适配器与本地网络连接 Virtu
  • 如何测试我的 Redis 缓存是否正常工作?

    我已经安装了 django redis cache 和 redis py 我遵循了 Django 的缓存文档 据我所知 以下设置就是我所需要的 但我如何判断它是否正常工作 设置 py CACHES default BACKEND redis
  • 将 Set> 转换为 HashMap

    在我的代码中的某一时刻 我创建了一个Set
  • 有没有办法在 ruby​​ 中重新定义 []=+

    我正在尝试编写一个简单的 DSL 针对 Redis 并且我想自己定义 I have def key val redis zadd name val key end 我想定义 def key val redis zincrby name va
  • Redis发布/订阅:查看当前订阅了哪些频道

    我目前有兴趣查看我拥有的 Redis 发布 订阅应用程序中订阅了哪些频道 当客户端连接到我们的服务器时 我们将它们注册到如下所示的通道 user user id 这样做的原因是我希望能够看到谁 在线 目前 我在不知道客户端是否在线的情况下盲
  • Node Js:Redis 作业在完成其任务后未完成

    希望你们做得很好 我在我的 Nodejs 项目中实现了 BullMQ Bull 的下一个主要版本 来安排发送电子邮件的作业 例如 发送忘记密码请求的电子邮件 所以 我编写了如下所示的代码 用户服务 await resetPasswordJo
  • Redis Cluster 与 Pub/Sub 中的 ZeroMQ,用于水平扩展的分布式系统

    如果我要设计一个巨大的分布式系统 其吞吐量应随系统中的订阅者数量和通道数量线性扩展 哪个会更好 1 Redis集群 仅适用于Redis 3 0 alpha 如果是集群模式 您可以在一个节点上发布并在另一个完全不同的节点上订阅 消息将传播并到
  • Laravel 所有会话 ID 与 Redis 驱动程序

    在我的应用程序中 我希望允许某些用户能够注销除他 她之外的所有其他用户 当会话驱动程序设置为文件时 我已经完成了此功能 但现在我使用 redis 作为会话驱动程序 并且我无法找到任何方法来列出所有当前会话 就像我在文件时所做的那样司机 问题

随机推荐

  • LiteOrm "cannot be instantiated"

    错误提示 java lang Class
  • 【深度强化学习】(5) DDPG 模型解析,附Pytorch完整代码

    大家好 今天和各位分享一下深度确定性策略梯度算法 Deterministic Policy Gradient DDPG 并基于 OpenAI 的 gym 环境完成一个小游戏 完整代码在我的 GitHub 中获得 https github c
  • 网络管理服务器篇之Apache

    一 软件简介 1 Apache是最流行的Web服务器端软件之一 快速 可靠 可通过简单的API扩展 Perl Python解释器可被编译到服务器中 完全免费 完全源代码开放 如果你需要创建一个每天有数百万人访问的Web服务器 Apache可
  • 【文件上传绕过】五、文件后缀大小写绕过

    文章目录 一 黑名单 二 源码 三 大小写绕过 一 黑名单 本pass禁止上传 php php5 php4 php3 php2 php1 html htm phtml pHp pHp5 pHp4 pHp3 pHp2 pHp1 Html Ht
  • String类详解

    目录 一 创建字符串的四种方式 1 直接赋值 2 通过构造方法创建对象 3 通过字符数组创建对象 4 通过String类的静态方法valueOf 任意数据类型 gt 转为字符串 二 字符串比较相等 equals方法 equalsIgnore
  • ICMP协议Ping方法的Python实现解析

    ICMP协议Ping方法的Python实现解析 说明 本代码适合Windows 没有在其他系统下进行测试 参考对象为https github com samuel python ping 流程 选择目标网址 解析对方ip地址 构造数据报 添
  • KVM 虚拟机的热迁移

    热迁移 顾名思义在虚拟机不关机的情况下将KVM虚拟机进行迁移 准备工作 两台KVM虚拟机 一台nfs虚拟机 centos7 4系统 主机 IP地址 主机名 KVM01 10 00 11 kvm01 KVM02 10 0 0 12 kvm02
  • SSM简单项目

    暑假项目实训花了2个星期做的项目 前台完成了大部分 后台做了一点点 其中主要运用了ssm框架技术 layui前端技术 拦截器 阿里云支付宝接口 阿里云短信验证接口 layui轮播图 流加载 分页 表单 表格等技术 访问网站 http www
  • 明智而审慎地使用多重继承——条款40

    当多重继承 multiple inheritance MI 进入设计景框 程序有可能从一个以上的base classes继承相同名称 如函数 typedef等等 那会导致较多的歧义机会 例如 class BorrowableItem 图书馆
  • 《unix环境高级编程》--- 进程环境

    终止码 main中返回一个整型值与用该值调用exit是等价的 include
  • jemalloc C++实践

    jemalloc是一种通用的malloc 3 实现 优点是避免内存碎片和可伸缩并发支持 下载源码 wget https github com jemalloc jemalloc releases download 5 2 1 jemallo
  • IA-YOLO项目中DIP模块的初级解读

    IA YOLO项目源自论文Image Adaptive YOLO for Object Detection in Adverse Weather Conditions 其提出端到端方式联合学习CNN PP和YOLOv3 这确保了CNN PP
  • mysql篇---windows环境

    1 windows环境下的mysql忘记密码了会很麻烦 试了好多种攻略都不行 只得重装 所以安装好后 要找个记事本写root密码 2 如果重装的话 直接到mysql官网 下载最新版 https dev mysql com downloads
  • 区块链:如何学习区块链技术?

    To strive to seek to find and not to yield 奋斗 探索 寻求 永不屈服 1 中本聪的关于比特币的白皮书 英文原版 Bitcoin A Peer to Peer Electronic Cash Sys
  • c++用vector先按学生的年级排序,再按学生的分数排序算法

    VectorSort cpp 定义控制台应用程序的入口点 include stdafx h include stdio h include string h include
  • PDF各种编辑方法(页码重排、解密、导入书签、导入注释、合并)

    目录 一 PDF重排页码 二 PDF解密 三 PDF导入和导出书签 四 PDF导入和导出注释 五 PDF合并 一 PDF重排页码 操作流程 如下图所示 打开 福昕高级PDF编辑器 gt 打开待处理的PDF文件 gt 点击软件界面左侧第二个图
  • js循环数组实现模糊匹配

  • Linux 下rm删除命令提示 /bin/rm: argument list too long的解决办法

    假设我们要删除文件夹test test下有很多文件 如果我们使用rm test 命令进行删除 则会出现 bin rm argument list too long无法删除的报错提示 报错提示原因 文件夹下的文件数目过多 命令行过长所致 解决
  • vue项目中引入高德地图

    近期在用vue做一个环保类的项目 要求使用高德地图 原生js api官方案例比较多 对于新手友好 但是在vue项目中加载是一个难以解决的问题 而专门为vue使用高德地图诞生的 vue AMap 组件听起来很美好 但由于需要学习高德原生语法和
  • Redis基础数据结构——有序集合

    Redis基础数据结构 有序集合 redis的有序集合zset类似于Java的SoretedSet和HashMap的结合体 一方面它是一个set 可以保证内部value的唯一性 另一方面它可以给每个value赋予一个score 代表这个sc