深入学习 Redis - 深挖经典数据类型之 zset

目录

前言

一、zset 类型

1.1、操作命令

zadd / zrange(添加 / 查询)

zcard(个数)

zcount(区间元素个数)

zrevrange(逆序展示)

zrangebyscore(按分数找元素)

zpopmax / zpopmin(删除)

bzpopmax / bzpopmin(阻塞删除)

zrank / zrevrank(排名)

zscore(分数)

zrem(删除) 

zremrangebyrank / zremrangebyscore(删除指定区间)

zincrby(增加分数)

交集、并集、差集说明

zinterstore / zunionstore(交集 / 并集)

1.2、内部编码方式

1.3、使用场景

排行榜系统


前言


redis 中所有的 key 都是字符串,value 的类型是存在差异的,因此出现了操控不同 value 的命令,接下来,就一起来学习一下吧~

Ps1:接下来,我给出的指令都是按照 Redis 官方文档的语法格式来解析的,[ ] 相当于一个独立的单元,表示可选项(可有可无),其中 | 表示 “或者” 的意思,多个只能出现一个,[ ] 和 [ ] 之间是可以同时存在的.

Ps2:一个快速失去年终奖的小技巧 —— 清除 redis 上所有的数据 =》 FLUSHALL,这个操作可以把 redis 上所有的键值对全部带走.

一、zset 类型


1.1、操作命令

Zset 是一个有序且唯一的集合,在 zset 中的 member 同时引入了一个属性 score(分数),支持浮点类型,也就是说每一个 member 都安排一个分数,并且默认按照分数进行升序排序.

Ps:zset 中的 member 和 score 不是键值对的关系,可以称为是一个 "pair",类似于 C++ 中的 std::pair。

对于有序集合来说,既可以通过 member 找到对应的 score ,又可以通过 score 找到匹配的 member。

zadd / zrange(添加 / 查询)

使用 zadd 往有序集合中,添加元素和分数.

时间复杂度是 O(log N),这是由于 zset 是有序结构,新增元素需要放在合适的位置上,底层是通过 跳表 这个数据结构实现的.

ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]

以下是对几个选项的说明:

  • NX | XX:的使用和之前 set 添加元素使用一样,这里值得注意的是 不加 NX | XX 选项时,当 member 不存在,此时就会达到 “添加新 member” 的效果,如果当前 member 已经存在,此时就会更新分数.
  • LT | GT:LT(less than)表示一旦要进行更新分数,如果分数比之前小,则更新成功,否则不跟新;GT(greater than)反之.
  • CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
  • INCR:此时命令类似 zincrby 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和 分数。

使用 zrange 查看 有序集合 中的元素详情(类似于 lrange 可以指定下标构成区间)

ZRANGE key start stop [WITHSCORES]

Ps:加上 withscores ,就可以连分数一起显示.

 

zcard(个数)

获取 zset 中的元素个数.

ZCARD key

zcount(区间元素个数)

获取分数在 min 和 max 之间(默认是闭区间)的元素个数,其中 min 和 max 本身就是浮点数类型,可以哦那个 inf 表示无穷大, -inf 表示 负无穷大.

ZCOUNT key min max

时间复杂度是 O(log N),具体执行流程如下:

先根据 min 找到对应的元素,再根据 max 找到对应的元素(这里的时间复杂度就是 O(log N) ),这里实际上 zset  内部会记录每个元素当前的 “排行 / 次序”,因此查到元素后就知道元素的 “次序”(下标),就可以直接把 max 对应的元素次序和 min 对应的元素次序,做减法即可.

Ps:如果想要排除边界值,可以使用括号,但是表示比较奇葩~,使用 ( 表示开区间,例如我要获取(70,98)这个区间的个数,就需要如下写法表示

一个好的设定是符合直觉的,但为什么这么设定?

这是为了考虑兼容性的原因:一旦在新的版本中引入和之前不兼容的特性,成本是非常高的(不兼容的案例最典型的就是 IPv6),一般来说,如果确实需要做出这种不兼容的修改,可以先把这个要修改的内容标记成 “弃用”(给程序员打个预防针),同时推出新的版本,若干个版本之后,再逐渐把这样的功能完成修改.

zrevrange(逆序展示)

zrevrange 中的 rev 就是 reverse 的缩写,表示逆序的意思。默认是升序,通过 zrevrange 就可以实现分数降序排序.

ZREVRANGE key start stop [WITHSCORES]

zrangebyscore(按分数找元素)

按照分数来找元素,和 zcount 类似.
 

ZRANGEBYSCORE key min max [WITHSCORES]

Ps:这个命令可能在 6.2.0 之后废弃,并且将功能合并到 zrange 中.

zpopmax / zpopmin(删除)

zpopmax 用来删除分数最高的 count 个元素.

zpopmin 用来删除分数最低的 count 个元素.

Ps:若分数一样,就按照字典序来删除其中的 count 个元素

ZPOPMAX key [count]
ZPOPMIN key [count]

时间复杂度为 O(log(N) * M),其中:

  • N 表示有序集合的元素个数.
  • M 表示要删除 count 个元素.

使用 zpopmax 删除的是最大值,在有序集合中就是最后一个元素(尾删),既然是尾删,为什么我们不把最后一个元素的位置的记录下来,后续删除不就直接就 O(1) 了嘛?

但是很遗憾, redis 目前并没有这么做~

事实上,redis 的源码中,针对有序集合,确实是记录尾部的位置,但在删除的时候,并没有用上这个特性,而是直接使用了一个 “通用的删除函数”(给定一个 member 的值,进行查找找到位置后再进行删除).

但这里我认为是存在优化空间的,确实可以通过记录+尾删达到 O(1) 的复杂度,但是这种优化的活要优化在刀刃上~  优化一般是要找到性能瓶颈,进行针对的优化,但是当前这个 log N 的速度也不慢,如果 N 不是夸张的大,基本是可以近似 O(N) 的~

比如去追一个妹子:

  1. 经常在她面前刷存在感,混个脸熟.(1个月)
  2. 约出来一起玩(叫上很多僚机). (2个月)
  3. 单独约出来玩.(3个月)
  4. 确定关系.(1年)

那么如果你在第一条上使劲优化,是没啥乱用的~

bzpopmax / bzpopmin(阻塞删除)

阻塞版本的 zpopmax / zpopmin,这里的 有序集合 可以认为是一个 带阻塞功能 的 "优先级队列",在队列为空的时候阻塞,直到其他客户端插入元素为止.

BZPOPMAX key [key ...] timeout
BZPOPMIN key [key ...] timeout

 timeout 表示超时时间,表示最多阻塞多久,单位是 s,支持小数形式.

时间复杂度是 log(N).

Ps:当 bzpopmax / bzpopmin 监听了多个 key,表示是从这若干个 key 中只删除一次.

 

zrank / zrevrank(排名)

zrank 和 zrevrank 都是返回指定元素的排名(下标),其中 zrank 按照升序排序,zrevrank 按照降序排序.

时间复杂度为 O(log N),主要是有个查询位置的过程(可以认为是在堆中查找元素)。

ZRANK key member
ZREVRANK key member

 

zscore(分数)

获取指定元素的分值.

ZSCORE key member

时间复杂度是 O(1),之前根据 member 找元素,都是 logN ,这里虽然也是要先找元素,但是 redis 对于此处的查询做了特殊的优化,付出了额外的空间代价.

zrem(删除) 

删除指定的 member 元素.

ZREM key member [member ...]

时间复杂度是 O(logN * M),其中 N 表示有序集合中元素的个数(因为要先找到元素),M 是 member 的个数.

zremrangebyrank / zremrangebyscore(删除指定区间)

zremrangebyrank 是指定下标描述范围来进行删除(元素升序排序).

zremrangebyscore 是指定分数区间进行删除.

Ps:区间都是闭合的(左闭右闭).

ZREMRANGEBYRANK key start stop
ZREMRANGEBYSCORE key min max

时间复杂度是 O(logN * M),其中 N 表示有序集合中元素的个数(因为要先找到元素),M 是要删除的元素个数.

zincrby(增加分数)

为指定的元素的关联分数添加指定的分数值。

ZINCRBY key increment member

Ps:修改分数后,任然保持升序. 

交集、并集、差集说明

zinter、zunion 、zdiff 这三个命令是从 redis 6.2 开始支持的,咱们使用的是 redis 5 ,此处不涉及.

zinterstore / zunionstore(交集 / 并集)

求出多个 key 之间的交集,并保存到一个新的集合中,合并的同时元素按照不同方式得到新的分值.

ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]

几个参数的说明:

destination:表明了要把结果存储到哪一个 zset 中.

numkeys:是一个整数,描述了后面又几个 key 参与交集运算(这样设定是为了将自定义的 key   和后面的参数区分开来,类似于 HTTP 协议中 Content-Length 的作用——解决 TCP 面向字节流传输的粘包问题).

weights:权重,相当于一个系数,会乘上当前的分数(类似于数学中的权值,假如我是一个好看有才华的妹子,那么追我的不同小哥哥身上都会有不同的权值,比如长得帅占5%、会舔占2%、有钱占93%......).

addregete:这里提供了三种参数,描述了相交的元素分数的处理方式(若不设置,默认是求和).

Ps:zunionstore 用法 和 zinterstore 基本一致.

 

1.2、内部编码方式

内部编码方式有以下两种:

  • skiplist:跳表,类似于 leetcode 上的一个经典题目,“复制带随机指针的链表”,跳表也是链表,不同于普通的链表,每一个节点上有多个指针域,巧妙的搭配这些指针域的指向就可以做到,从跳表上查询元素的时间复杂度是 O(logN),相比于树形结构,更适合范围获取元素.
  • ziplist:压缩列表,当哈希表里的元素比较少的时候,就优化成了 ziplist 了,能够节省空间(压缩的原因:redis 上有很多 key,可能某些 key 的 value 是 hash,此时如果 key 特别多,对应的 hash 也特别多,但是每个 hash 又不是特别大的情况下,就尽量去压缩,让整体占用内存更小了).

Ps:如果有序集合中元素较少,或者单个元素体积较小,使用 ziplist 来存储,这样更节省内存空间。如果元素较多,或者单体过大,就是用 skiplist 来存储了.

1.3、使用场景

排行榜系统

这种场景有很多:微博热搜、游戏天梯排行、成绩排行......

例如使用 zset 来实现 游戏天梯排行,只需要把玩家的信息和对应的分数给放到有序集合中即可,自动就生成一个排行榜,随时可以按照排行(下标),按照分数,进行范围查询~

也可以通过 zincrby 来修改分数,排行也能自适应调整(logN).

玩家那么多,zset 能存下吗?

userId 4 个字节 score 8 个字节来理解,那么表示一个玩家大概是 12 个字节,往多了算,1 亿玩家,大概是 12亿 字节 => 1.2 GB(这里的单位换算一定要张口就来~ 影响升职加薪),这大么?

游戏排行榜的先后顺序还是比较容易确定的,但是像 微博热度 这种就不太好算~

他是根据综合数值来衡量的:

  1. 浏览量
  2. 点赞量
  3. 转发量
  4. 评论量

这就需要根据每一个维度的权重,来计算综合得分,此时就可以借助 zinterstore / zunionstore 按照加权的方式来处理了,具体的,member 就是 微博 的 id,而 score 就是通过 zinterstore / zunionstore 按照约定好的权重,进行集合运算即可,最后得到的集合分数就是热度,排行榜也就出来了.

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【UE5 多人联机教程】06-显示玩家名称

效果 可以看到玩家输入各自的名称&#xff0c;会显示到自己控制的角色头上。但是目前有一个BUG就是&#xff0c;当客户端加入游戏时会多创建一个服务端的角色。 步骤 1. 打开“BP_ThirdPersonCharacter”&#xff0c;添加一个控件组件&#xff0c;用于显示玩家名称 作为网格体…

内存函数讲解

&#x1f495;"痛苦难以避免&#xff0c;而磨难可以选择。"-->村上春树&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;数据在内存中的存储 内存函数就是管理内存数据的函数&#xff0c;包含于头文件<string.h>中 1.memcpy函数-->内存…

Jenkins插件管理切换国内源地址

一、替换国内插件下载地址 选择系统管理–>插件管理–> Available Plugins 并等待页面完全加载完成、这样做是为了把jenkins官方的插件列表下载到本地、接着修改地址文件、替换为国内插件地址 进入插件文件目录 cd /var/lib/jenkins/updatesdefault.json 为插件源地址…

tinymce实现将word中内容(文字图片等)直接粘贴至编辑器中——利用插件tinymce-powerpaste-plugin

TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。同类程序有&#xff1a;UEditor、Kindeditor、Simditor、CKEditor、wangEditor、Suneditor、froala等等。 TinyMCE的优势&#xff1a; 开源可商用&#xff0c;基于LGPL2.1 插件丰富&#xff0c;自带插件基本涵盖日常…

给APK签名—两种方式(flutter android 安装包)

前提&#xff1a;给未签名的apk签名&#xff0c;可以先检查下apk有没有签名 通过命令行查看&#xff1a;打开终端或命令行界面&#xff0c;导入包含APK文件的目录&#xff0c;并执行以下命令&#xff1a; keytool -printcert -jarfile your_app.apk 将 your_app.apk替换为要检查…

【数据结构】--189.轮转数组

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Rethinking the Image Fusion(PMGI)

1.摘要 本文提出了一种基于梯度和强度比例维护&#xff08;PMGI&#xff09;的快速统一图像融合网络&#xff0c;可以端到端实现各种图像融合任务&#xff0c;包括红外和可见图像融合、多曝光图像融合、医学图像融合、多焦点图像融合和全色增强。我们将图像融合问题统一为源图…

保姆级系列教程-玩转Fiddler抓包教程(1)-HTTP和HTTPS基础知识

1.简介 有的小伙伴或者童鞋们可能会好奇地问&#xff0c;不是讲解和分享抓包工具了怎么这里开始讲解HTTP和HTTPS协议了。这是因为你对HTTP协议越了解&#xff0c;你就能越掌握Fiddler的使用方法&#xff0c;反过来你越使用Fiddler&#xff0c;就越能帮助你了解HTTP协议。 Fid…

Java | 继承、多态、抽象类与接口

目录 一、类的继承 二、Object类 2.1 getClass()方法 2.2 toString()方法 2.3 equals()方法 三 、对象类型的转换 3.1 向上转换 3.2 向下转型 四、使用instanceof关键字判断对象类型 五、方法的重载 六、final关键字 6.1 final变量 6.2 final方法 6.3 final类 七…

【多模态】19、RegionCLIP | 基于 Region 来实现视觉语言模型预训练

文章目录 一、背景二、方法2.1 Region-based Language-Image Pretraining2.2 目标检测的迁移学习 三、效果3.1 数据集3.2 实现细节3.3 结果 论文&#xff1a; RegionCLIP: Region-based Language-Image Pretraining 代码&#xff1a;https://github.com/microsoft/RegionCLIP …

了解Unity编辑器之组件篇Playables和Rendering(十)

Playables 一、Playable Director&#xff1a;是一种用于控制和管理剧情、动画和音频的工具。它作为一个中央控制器&#xff0c;可以管理播放动画剧情、视频剧情和音频剧情&#xff0c;以及它们之间的时间、顺序和交互。 Playable Director组件具有以下作用&#xff1a; 剧情控…

Vue中使用echarts

1 安装 npm install -g cnpm --registryhttps://registry.npm.taobao.org cnpm install echarts -S 2 main.js引入 3 引入成功

探索Java API学习路线:从基础到高级的全面指南

文章目录 第一阶段&#xff1a;入门基础1. 环境准备2. 学习Java基础 第二阶段&#xff1a;熟悉常用的Java API1. Java标准库2. Java API文档 第三阶段&#xff1a;深入学习特定领域的Java API1. Java GUI API2. Java数据库连接&#xff08;JDBC&#xff09;API3. Java多线程API…

【蓝图】p44简单解密机关

p44简单解密机关 p44简单解密机关文字提示开门文字提示开灯For Each Loop和For Each Loop With Break区别For Each LoopFor Each Loop With Break小操作&#xff1a;改变走线Execute Console Command(执行控制台命令) p44简单解密机关 文字提示开门 创建Actor蓝图类&#xff…

Python及PyCharm安装教程

1.Python安装教程 python官网下载windows64位python installer&#xff1b; 这里选择windows installer(64-bit) 打开下载完成的installer文件 记得勾选上Add python.exe to PATH&#xff0c;再点击Install Now&#xff1b; &#xff08;如果想要更改安装位置&#xff0c;需要…

【使用时空RBF-NN进行非线性系统识别】实现了 RBF、分数 RBF 和时空 RBF 神经网络,用于非线性系统识别研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 2.1 算例1 2.2 算例2 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 本文用于非线性系统识别任务的径向基函数神经网络&#xff08;RBF-NN&#xff09;的三种变体。特别是&#xff0c;我实现…

redis到底几个线程?

通常我们说redis是单线程指的是从接收客户端请求->解析请求->读写->响应客户端这整个过程是由一个线程来完成的。这并不意味着redis在任何场景、任何版本下都只有一个线程 为何用单线程处理数据读写&#xff1f; 内存数据储存已经很快了 redis相比于mysql等数据库是…

宋浩高等数学笔记(八)向量代数与空间解析几何

本章知识点并不难理解&#xff0c;但是公式与名词属于非常多&#xff0c;记忆时需重点对待。

lc154.寻找旋转排序数组中的最小值

最小元素的位置以旋转次数为索引的位置&#xff0c;但是没有告诉旋转次数&#xff0c;换一种思路 当遇到arr[index] > arr[index1]时&#xff0c;index1为最小元素的位置。首位位置独立比较。但是这种方法还是遍历数组 观察两组数的中间值与首尾的值&#xff0c;又由于数组…

vue动态引入静态资源

vue动态引入静态资源 静态资源位置&#xff08;../../assets/piecture/page404.jpg&#xff09;或者&#xff08;/assets/piecture/page404.jpg&#xff09; 错误引入方式 错误引入方式&#xff08;一&#xff09; <template><div><img :src"../../asset…