[Redis]1-高效的数据结构P2-Set
按照惯例,先丢一个官网文档链接。
上篇我们已经了解了高效的数据结构P1-String与Hash。
这篇,我们继续来了解Redis的 Set 与 Sorted set。
目录
- 有序集合 Sorted set
- 底层实现
- 集合 Set
- 总结
- 资料引用
有序集合 Sorted set
Redis 有序集合是一组唯一的字符串(成员)集合,这些字符串根据一个关联的分数进行排序。
这种有序、元素唯一且根据关联的分数进行排序的高效操作的数据结构,简称ZSET。
可以用于:
- 动态排序:比如排行榜,每个元素可以代表一个实体(如用户、商品),分数表示排序依据(如积分、销量)。由于ZSET自动维护排序,你可以轻松获取排名靠前的成员、某个成员的排名,或者按分数范围查询。
# 添加
> zadd prices 8 sandwich
(integer) 1> zadd prices 100000 car
(integer) 1> zadd prices 6300 iphone 8900 iphonepro
(integer) 2# 结果展示
> zrange prices 0 9 withscores
1) "sandwich"
2) "8"
3) "iphone"
4) "6300"
5) "iphonepro"
6) "8900"
7) "car"
8) "100000"
- 速率限制器:ZSET可以实现一种基于滑动窗口的速率限制器,利用时间戳作为分数,成员记录请求标识,自动移除过期的请求。
底层实现
ZSET通过包含跳表和哈希表的二端口数据结构实现。每个ZSET对象包含一个哈希表和一个跳表,成员和分数在两边各存一份,哈希表存member -> score,跳表存score -> member的排序关系。
typedef struct zset {dict *dict;zskiplist *zsl;
} zset;
其中哈希结构上章已经了解过了。ZSET的唯一性便是通过Hash Table实现的。总体结构也同Hash类似。
跳表用于维护成员的按分数排序,支持高效的插入、删除、排名查询和范围查询。
本篇进行跳表skiplist的介绍,并了解skiplist是如何设计以支持排序的。
源码地址点这,其结构由redis.h/zskiplistNode和redis.h/zskiplist两个结构定义。
zskiplist和zskiplistNode结构如下:
typedef struct zskiplist {struct zskiplistNode *header, *tail; //头、尾节点unsigned long length; // 长度int level;//记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)
} zskiplist;typedef struct zskiplistNode {sds ele; // 成员double score; // 分数struct zskiplistNode *backward;// 后退指针struct zskiplistLevel {struct zskiplistNode *forward;// 前进指针unsigned long span;// 跨度,记录跳过的节点数(前进指针所指向节点和当前节点的距离)} level[];
};
zskiplistNode用于表示跳跃表节点,zskiplist结构用于保存跳跃表节点的相关信息。
zskiplistNode点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快。
较传统Node与List,zskiplistNode多了一个level[]结构,这是一个动态数组。每个元素表示该节点在某一层级的前进指针(指向下一个节点)和跨度(span,表示跳过的节点数)
struct zskiplistLevel {struct zskiplistNode *forward;// 前进指针unsigned long span;// 跨度,记录跳过的节点数(前进指针所指向节点和当前节点的距离)} level[];
Redis zskiplistNode这个设计通过level[]存储的多层索引预计算节点关系(预存关系),让查找、插入和删除的复杂度从传统链表的O(N)降到O(log N),接近二分查找的效率。
跳表的高层索引节点稀疏,低层节点密集,类似二分搜索的层次划分,快速定位目标节点或分数范围。
level[]有点类似闭包表的核心表设计,存储节点关系。
![![[Pasted image 20250417234839.png]]](https://i-blog.csdnimg.cn/direct/023f6a4da5a048fdbaaf56a839fe7b9f.png)
- 核心方法
阅读Redis的zskiplist.c,重点看zslInsert(插入)和zslGetRank(排名计算),理解level[]和span的实现。
zslInsert源码如下:
/* 插入一个新节点到跳表中,返回新插入的节点指针* 参数:* zsl: 跳表对象* score: 新节点的分数* ele: 新节点的成员(字符串)* 返回:* 新插入的节点指针*/
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x; // update数组记录每层插入位置的前驱节点unsigned long rank[ZSKIPLIST_MAXLEVEL]; // rank数组记录每层累积的跨度int i, level;serverAssert(!isnan(score)); // 确保分数不是NaN// 步骤1:查找插入位置,记录前驱节点和跨度x = zsl->header; // 从头节点开始for (i = zsl->level-1; i >= 0; i--) { // 从最高层逐层向下查找// 初始化rank,继承上一层的跨度(若非最高层)rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];// 沿当前层前进,直到遇到分数更大或字典序更大的节点while (x->level[i].forward &&(x->level[i].forward->score < score ||(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; // 前进到下一个节点}// 记录当前层的前驱节点update[i] = x;}// 步骤2:随机生成新节点的层级level = zslRandomLevel(); // 随机层级,概率递减(通常p=0.25)if (level > zsl->level) { // 如果新层级超过当前最大层级for (i = zsl->level; i < level; i++) {rank[i] = 0; // 新层级的rank初始化为0update[i] = zsl->header; // 新层级的前驱是头节点update[i]->level[i].span = zsl->length; // 跨度设为跳表总长度}zsl->level = level; // 更新跳表最大层级}// 步骤3:创建新节点x = zslCreateNode(level, score, ele); // 分配新节点内存,初始化分数和成员for (i = 0; i < level; i++) { // 为每层设置指针和跨度// 插入新节点:将新节点的forward指向前驱的forwardx->level[i].forward = update[i]->level[i].forward;update[i]->level[i].forward = x; // 前驱指向新节点// 更新跨度:新节点的跨度 = 前驱原跨度 - 已跳过的节点数x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);update[i]->level[i].span = (rank[0] - rank[i]) + 1; // 前驱的新跨度}// 步骤4:处理更高层的跨度for (i = level; i < zsl->level; i++) {update[i]->level[i].span++; // 未插入新节点的层,跨度+1}// 步骤5:设置后退指针x->backward = (update[0] == zsl->header) ? NULL : update[0];if (x->level[0].forward)x->level[0].forward->backward = x;elsezsl->tail = x;// 步骤6:更新跳表元数据zsl->length++; // 跳表长度+1return x; // 返回新节点
}
集合 Set
Redis 集合是一个无序且唯一的字符串集合(成员)。您可以使用 Redis 集合高效地:
- 跟踪唯一的项目(例如,跟踪访问特定博客文章的所有唯一 IP 地址。唯一事件ID)
- 表示关系(例如,具有给定角色的所有用户的集合)
- 执行常见的集合操作,如交集、并集和差集
和Java的HashSet一样,非常适合删除重复数据的集合。
Redis Set的底层实现主要依赖两种数据结构:哈希表(Hash Table)和整数集合(Intset)。
其中哈希结构上章已经了解过了。唯一性便是通过Hash Table实现的。那么整数集合Intset是干什么的呢?
用来提供动态数据结构选择的。
当Set包含非整数成员(如字符串)或成员数量较多时,使用哈希表。
当Set的所有成员都是整数(支持int16、int32、int64),且成员数量较少时(受set-max-intset-entries配置控制,默认512),使用整数集合Intset。
数量超过阈值会转换成Hash Table,且Intset到哈希表的转换是单向的(不可逆),因为哈希表支持任意字符串,而Intset只支持整数。
即,Redis Set的底层数据结构会根据存储的成员类型和数量动态选择。
intset源码地址。
源码如下:
typedef struct intset {// 编码类型(INTSET_ENC_INT16/32/64)uint32_t encoding;// 数组长度uint32_t length;// 保存元素的数组int8_t contents[];
} intset;
Intset是一个紧凑的有序数组,存储整数值,自动选择最小编码类型(int16_t、int32_t、int64_t)以节省内存。
Intset有编码升级机制:
当插入的整数超出当前编码范围(如int16_t溢出),Intset自动升级到更高编码(如int32_t),并重新分配内存。
且Intset有序。Intset按数值大小排序,插入时使用二分查找定位。
Redis通过设计一种转换机制,使用Intset来专门优化存储小规模整数集合,达到节省内存(紧凑存储)的目的,提升内存效率,且支持快速二分查找,适合小集合的查询。
总结
Redis不负简单高效的内存数据库之名,一方面做了大量空间换时间的操作,一方面设计极致压榨内存、提升内存效率。
比如跳表的预存、hash表的渐进扩容、String sds的预留空间、延迟释放、intset的极致内存利用、set的动态转换。
资料引用
《Redis设计与实现》
相关文章:
[Redis]1-高效的数据结构P2-Set
按照惯例,先丢一个官网文档链接。 上篇我们已经了解了高效的数据结构P1-String与Hash。 这篇,我们继续来了解Redis的 Set 与 Sorted set。 目录 有序集合 Sorted set底层实现 集合 Set总结资料引用 有序集合 Sorted set Redis 有序集合是一组唯一的字符…...
在ubuntu20.04上安装ros2
1,更新系统并安装依赖 sudo apt update sudo apt upgrade sudo apt install curl gnupg2 lsb-release2,增加ROS2仓库配置 echo "deb [archamd64] https://packages.ros.org/ros2/ubuntu focal main" | sudo tee /etc/apt/sources.list.d/ros…...
用ffmpeg 实现拉取h265的flv视频转存成264的mp4 实现方案
参考文章 支持 flvh265 的ffmpeg编译安装_demuxer flvhevc异常-CSDN博客 windwos有别人编译好的 支持HEVC/H265 RTMP播放的FFMPEG/FFPLAY WINDOWS版本 但是linux没有所以得自己编译 1.需要对ffmpeg进行源码修改 这里使用 https://github.com/numberwolf/FFmpeg-QuQi-H265-…...
解决“驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接“问题
参考链接: https://blog.csdn.net/yyj12138/article/details/123073146...
[密码学实战]基于Python的国密算法与通用密码学工具箱
引言 在当今数字化浪潮中,信息安全已成为个人隐私保护与商业机密守护的核心议题。作为一位在密码学领域深耕多年的技术实践者,我深谙密码学工具在构建数字安全防线中的关键作用。正是基于这份认知与责任,我倾力打造了一款全方位、高性能的密码学工具,专为满足广大用户在日…...
论文降重GPT指令-实侧有效从98%降低到8%
步骤1:文本接收 指令: 请用户提供需要优化的文本内容。 对文本进行初步分析,识别文本的基本结构和风格。 操作: 接收并分析用户提交的文本。 步骤2:文本优化 2.1 连接词处理 指令: 删除或替换连接词&#x…...
Compose Multiplatform Android Logcat工具
一、通过adb发送指令,收集设备日志并保存 二、UI 三、代码 /*** 获取设备列表*/fun getDevices(): List<String> {val process ProcessBuilder("adb", "devices").redirectErrorStream(true).start()val output process.inputStream.…...
[渗透测试]渗透测试靶场docker搭建 — —全集
[渗透测试]渗透测试靶场docker搭建 — —全集 对于初学者来说,仅仅了解漏洞原理是不够的,还需要进行实操。对于公网上的服务我们肯定不能轻易验证某些漏洞,否则可能触犯法律。这是就需要用到靶场。 本文主要给大家介绍几种常见漏洞对应的靶场…...
JavaScript 渲染内容爬取:Puppeteer 入门
在现代网络应用中,许多网页内容是通过 JavaScript 渲染生成的,传统的爬虫工具往往难以获取这些动态内容。Puppeteer 作为一种强大的浏览器自动化工具,为这一问题提供了优雅的解决方案。本文将带你入门 Puppeteer,介绍如何安装、启…...
Ubuntu 系统下安装和使用性能分析工具 perf
在 Ubuntu 系统下安装和使用性能分析工具 perf 的步骤如下: 1. 安装 perf perf 是 Linux 内核的一部分,通常通过安装 linux-tools 包获取: # 更新软件包列表 sudo apt update# 安装 perf(根据当前内核版本自动匹配) …...
神经网络:从基础到应用,开启智能时代的大门
在当今数字化时代,神经网络已经成为人工智能领域最热门的技术之一。从语音识别到图像分类,从自然语言处理到自动驾驶,神经网络的应用无处不在。它不仅改变了我们的生活方式,还为各个行业带来了前所未有的变革。本文将带你深入了解…...
人工智能-机器学习(线性回归,逻辑回归,聚类)
人工智能概述 人工智能分为:符号学习,机器学习。 机器学习是实现人工智能的一种方法,深度学习是实现机器学习的一种技术。 机器学习:使用算法来解析数据,从中学习,然后对真实世界中是事务进行决策和预测。如垃圾邮件检…...
密码明文放在请求体是否有安全隐患?
明文密码放在请求体中是有安全隐患的,但这个问题可以被控制和缓解,关键在于是否采取了正确的安全措施。 ⚠️ 为什么明文密码有风险? 中间人攻击(MitM): 如果使用 HTTP 明文传输,攻击者可以在数…...
EMQX学习笔记
MQTT简介 MQTT是一种基于发布订阅模式的消息传输协议 消息:设备和设备之间传输的数据,或者服务和服务之间传输的数据 协议:传输数据时所遵循的规则 轻量级:MQTT协议占用的请求源较少,数据报文较小 可靠较强ÿ…...
探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因
文章目录 一、问题背景二、问题原因三、问题探析Kotlin空指针校验Gson.fromJson(String json, Class<T> classOfT)TypeTokenGson.fromJson(JsonReader reader, TypeToken<T> typeOfT)TypeAdapter 和 TypeAdapterFactoryReflectiveTypeAdapterFactoryRecordAdapter …...
冰川流域提取分析——ArcGIS pro
一、河网提取和流域提取视频详细GIS小熊 || 6分钟学会水文分析—河网提取(以宜宾市为例)_哔哩哔哩_bilibili 首先你要生成研究区域DEM,然后依次是填洼→流向→流量→栅格计算器→河网分级→栅格河网矢量化(得到河网.shpÿ…...
wordpress 垂直越权(CVE=2021-21389)漏洞复现详细教程
关于本地化搭建vulfocus靶场的师傅可以参考我置顶文章 KALI搭建log4j2靶场及漏洞复现全流程-CSDN博客https://blog.csdn.net/2301_78255681/article/details/147286844 描述: BuddyPress 是一个用于构建社区站点的开源 WordPress 插件。在 7.2.1 之前的 5.0.0 版本的 BuddyP…...
MySQL 线上大表 DDL 如何避免锁表(pt-online-schema-change)
文章目录 1、锁表问题2、pt-online-schema-change 原理3、pt-online-schema-change 实战3.1、准备数据3.2、安装工具3.3、模拟锁表3.4、解决锁表 1、锁表问题 在系统研发过程中,随着业务需求千变万化,避免不了调整线上MySQL DDL数据表的操作,…...
uni-app 状态管理深度解析:Vuex 与全局方案实战指南
uni-app 状态管理深度解析:Vuex 与全局方案实战指南 一、Vuex 使用示例 1. 基础 Vuex 配置 1.1 项目结构 src/ ├── store/ │ ├── index.js # 主入口文件 │ └── modules/ │ └── counter.js # 计数器模块 └── main.js …...
剑指offer经典题目(五)
目录 栈相关 二叉树相关 栈相关 题目一:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的 min 函数,输入操作时保证 pop、top 和 min 函数操作时,栈中一定有元素。OJ地址 图示如下。 主要思想:我们…...
3、排序算法1---按考研大纲做的
一、插入排序 1、直接插入排序 推荐先看这个视频 1.1、原理 第一步,索引0的位置是有序区(有序区就是有序的部分,刚开始就只有第一个数据是有序的)。第二步,将第2个位置到最后一个位置的元素,依次进行排…...
llama-webui docker实现界面部署
1. 启动ollama服务 [nlp server]$ ollama serve 2025/04/21 14:18:23 routes.go:1007: INFO server config env"map[OLLAMA_DEBUG:false OLLAMA_FLASH_ATTENTION:false OLLAMA_HOST: OLLAMA_KEEP_ALIVE:24h OLLAMA_LLM_LIBRARY: OLLAMA_MAX_LOADED_MODELS:4 OLLAMA_MAX_…...
jinjia2将后端传至前端的字典变量转换为JS变量
后端 country_dict {AE: .amazon.ae, AU: .amazon.com.au} 前端 const country_list JSON.parse({{ country_list | tojson | safe }});...
如何深入理解引用监视器,安全标识以及访问控制模型与资产安全之间的关系
一、核心概念总结 安全标识(策略决策的 “信息载体) 是主体(如用户、进程)和客体(如文件、数据库、设备)的安全属性,用于标记其安全等级、权限、访问能力或受保护级别,即用于标识其安全等级、权限范围或约束…...
Linux的Socket开发补充
是listen函数阻塞等待连接,还是accept函数阻塞等待连接? 这两个函数的名字,听起来像listen一直在阻塞监听,有连接了就accept,但其实不是的。 调用listen()后,程序会立即返回,继续执行后续代码&a…...
Flutter异常Couldn‘t find dynamic library in default locations
Flutter项目在Windows系统使用ffigen生成代码时报下面的错误: [SEVERE] : Couldnt find dynamic library in default locations. [SEVERE] : Please supply one or more path/to/llvm in ffigens config under the key llvm-path. Unhandled exception: Exception: …...
Spring-AOP分析
Spring分析-AOP 1.案例引入 在上一篇文章中,【Spring–IOC】【https://www.cnblogs.com/jackjavacpp/p/18829545】,我们了解到了IOC容器的创建过程,在文末也提到了AOP相关,但是没有作细致分析,这篇文章就结合示例&am…...
[特殊字符] Prompt如何驱动大模型对本地文件实现自主变更:Cline技术深度解析
在AI技术快速发展的今天,编程方式正在经历一场革命性的变革。从传统的"人写代码"到"AI辅助编程",再到"AI自主编程",开发效率得到了质的提升。Cline作为一款基于VSCode的AI编程助手,通过其独特的pro…...
【专业解读:Semantic Kernel(SK)】大语言模型与传统编程的桥梁
目录 Start:什么是Semantic Kernel? 一、Semantic Kernel的本质:AI时代的操作系统内核 1.1 重新定义LLM的应用边界 1.2 技术定位对比 二、SK框架的六大核心组件与技术实现 2.1 内核(Kernel):智能任务调度中心 2…...
PHP 8 中的 Swow:高性能纯协程网络通信引擎
一、什么是 Swow? Swow 是一个高性能的纯协程网络通信引擎,专为 PHP 设计。它结合了最小化的 C 核心和 PHP 代码,旨在提供高性能的网络编程支持。Swow 的核心目标是释放 PHP 在高并发场景下的真正潜力,同时保持代码的简洁和易用性…...
