【Redis】zset数据类型相关指令

zset简单介绍

有序集合相对于字符串、列表、哈希、集合来说会有一些陌生。它保留了集合不能有重复成员的特点,但与集合不同的是,有序集合中的每个元素都有一个唯一的浮点类型的分数(score)与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是用下标作为排序依据而是用这个分数。这里的有序和 list 的有序又是不同的有序,zset 的有序是明确的升序降序的顺序(默认查询是升序,但是也能降序,所以我们认为其就是有顺序,升序降序都是),list 的有序是按照下标的顺序。有序集合中的元素是不能重复的,但分数允许重复。类比于一次考试之后,每个人一定有一个唯一的分数,但分数允许相同。

数据结构 是否允许重复元素 是否有序 有序依据 应用场景
列表 索引下标 时间轴、消息队列等
集合 - 标签、社交等
有序集合 分数 排行榜系统、社交等

zadd

添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。

不带选项的使用就是将 score 和 member 添加进 zset 中,这里不是 key - value 的结构,因为后面我们会知道 score 和 member 可以互相寻找,这里我们称这两个组成一个 pair。如果 key 不存在就会创建。

除了最基本的使用,zadd还有多个选项:

XX:仅仅用于更新已经存在的元素,不会添加新元素。

NX:仅用于添加新元素,不会更新已经存在的元素。

LT:仅当新分值小于当前分值时,更新已存在的元素。此标志不阻止添加新元素。

GT:仅当新分值大于当前分值时,更新已存在的元素。此标志不阻止添加新元素。

CH:将返回值从 "新增元素的数量" 修改为 "发生变更的元素总数"(CH 是 changed 的缩写)。发生变更的元素包括新增的元素,以及分值被更新的已有元素。因此,命令行中指定的元素若分值与原有分值相同,则不计入统计。注:默认情况下,ZADD 的返回值仅统计新增元素的数量。

INCR:指定此选项时,ZADD 的行为等同于 ZINCRBY。此模式下仅能指定一组 "分值 - 元素" 对。

注:GT、LT 和 NX 选项互斥。在 Redis 中,\[\] 表示可有可无,\[\] 中的 | 表示或,二选一互斥的意思。

注意这里的下标也支持倒数。

注意 zadd 的指令返回的是新增的元素数,所以如果只是更新数值,那么会返回0,这点在使用改值指令时较为不直观,这也是选项 CH 的存在意义。

incr 选项不支持多个 score - member,可以通过负数实现减法。

该指令的时间复杂度为每个 score - member O(log(N)),因为其底层是跳表,优化查找时间复杂度为 log(N),这里的 N 是元素数,如果查找 M 个 score - member,时间复杂度为 M * O(log(N))。

此外,之前说过 Redis 支持重复 score,如果 score 相同,则是会按照 members 以字典序进行排序,如果是降序则是反着的字典序。

最后,分数支持小数操作,也支持 -inf 和 inf(Redis 中 zset 分值支持的特殊浮点数值,本质对应数学里的 "无穷大 / 无穷小" 概念),不过不支持会导致 NaN(非数字)的无穷值运算(核心是 inf - inf/-inf + inf 这类运算,而非所有 -inf 和 inf 之间的计算)。

zcard

获取一个 zset 的基数(cardinality),即 zset 中的元素个数。

zcount

返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。

这里我们可以使用 ( 表示开区间,但是奇葩的是无论左右都是用的 (。这个指令的时间复杂度是 O(log(N)),为什么呢?如果按照正常想法,知道 min 和 max 之间的元素数不应该是先根据 min 和 max 找到对应的元素,再去遍历元素之间的值计数吗,其实 zset 的内部记录每个元素的排名,查询元素的时间复杂度之前也说了是 O(log(N)) ,当我们找到了元素,就能根据一头一尾的排名相减得出答案。

zrange

返回指定区间里的元素,分数按照升序。带上 withscores 可以把分数也返回。

使用 withscores 可以加上 score。可以看到如果改了 score,排序自动变。

6.2版本之后,加上了几个选项:

BYSCORE:按分值范围查询(替代下标范围),把 start/stop 从 "下标" 改成 "分值",查询分值在 start, stop 区间的元素。

BYLEX:按元素字典序范围查询(需元素分值相同),把 start/stop 从 "下标" 改成 "字符串字典序",仅当 zset 所有元素分值相同时有效。

REV:反向排序(降序)。

LIMIT offset count:分页(仅在 BYSCORE/BYLEX 后生效),对查询结果做分页,跳过 offset 个元素,返回最多 count 个元素。

也因此 zrange 可以替换一些指令,这些指令之后可能会被优化掉(zrevrange,zrangebyscore,zrevrangebyscore,zrangebylex,zrevrangebylex)。

zrevrange

返回指定区间里的元素,分数按照降序。带上 withscores 可以把分数也返回。

已被标记废弃。

zrangebyscore

返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。

已被标记废弃。

zpopmax

删除并返回分数最高的 count 个元素。

可以用来解决一些 topk 问题。注意这里的时间复杂度是 O(log(N) * M),N 是元素数,M 是 count。这里的 popmax 其实就是尾删,那么根据之前的经验,为什么不优化一些,记录一下尾部,然后 O(1) 删除呢?猜测是性能瓶颈不大,就没有优化。

bzpopmax

ZPOPMAX 的阻塞版本。

返回结果会带上 key,因为可以监视多个 key,且搞到一个结果就直接返回,所以时间复杂度是 O(log(N))。

timeout(超时时间)参数被解析为一个双精度浮点数,用于指定最大阻塞时长(以秒为单位)。超时时间设为 0 时,表示永久阻塞。

zpopmin

删除并返回分数最低的 count 个元素。

bzpopmin

ZPOPMAX 的阻塞版本。

zrank

返回指定元素的排名,升序。

和 zcount 差不多,而是找到元素,直接得到排名。

zrevrank

返回指定元素的排名,降序。

zscore

返回指定元素的分数。

为什么这里的时间复杂度是 O(1) 呢?可以认为这里有比较大的性能瓶颈,付出了内存的代价,提升了时间复杂度。

zrem

删除指定的元素。

zremrangebyrank

按照排序,升序删除指定范围的元素,左闭右闭。

zremrangebyscore

按照分数删除指定范围的元素,左闭右闭。

zincrby

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

zinter

求出给定有序集合中元素的交集,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

这里的求并集以 member 为基准的,score 只是附属,不只是这,整个 zset 也是如此,score 都是附属,member 才是元素本身。这里的 weights 指的权重,也就是按照顺序对应的 key 中的元素合并时的重要性,讲人话就是对应 key 的 weights 是几,合并时就会将这个 key 中的元素乘上几,然后按照合并规则来(sum求和,max求最大、min求最小)。这里的 aggregate 就是合并规则,sum求和,max取最大、min求取最小,不写默认求和。注意我们的 key 之前有一个 numkeys,指明我们要写几个 key,因为 key 的后面也有选项,所以如果不这么搞分不清选项和参数了,后面的参数也是可以有多个,所以开头要有 weights、aggregate,好区分解析。

注意该指令的时间复杂度是 O(NK) + O(Mlog(M))。N 是输入的有序集合中,最小的有序集合的元素个数;K 是输入了几个有序集合;M 是最终结果的有序集合的元素个数。

zunion

求出给定有序集合中元素的并集,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

规则和 zinter 一致。

zinterstore

求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

如果目标 key 不存在会自动创建。

zunionstore

求出给定有序集合中元素的并集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。

编码方式

有序集合类型的内部编码有两种:
ziplist(压缩列表) :当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用 ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
skiplist(跳表):当 ziplist 条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降。

使用场景

有序集合最典型的使用场景是排行榜系统,比如网站的多维度热榜(时间、阅读量、点赞量等),以点赞数维度维护每日热榜为例,可通过 zset 相关命令完成全流程操作:在添加用户赞数时,可先用 zadd 为用户初始化点赞数,后续新增点赞则通过 zincrby 实现赞数的增量更新;当因用户注销、作弊等原因需要取消用户赞数时,可使用 zrem 将目标用户从对应日期的榜单中删除;要展示点赞数最多的 10 个用户,可借助 zrevrange 命令按分值降序获取排名前 10 的用户;若需展示用户详细信息及对应的赞数、排名,可将用户信息存储在哈希类型中,再通过 zscore 获取用户具体的点赞分数,通过 zrank 获取用户在榜单中的排名。

相关推荐
憧憬成为web高手10 小时前
皮卡丘靶场速通--sql 2
数据库·sql·mybatis
段一凡-华北理工大学10 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
二宝哥11 小时前
CentOS 7.9 系统下 Redis 5.0.4 单机安装与源码编译安装指南
redis·centos
oradh12 小时前
Oracle XTTS实现跨版本迁移和升级(Oracle 11g单库升级至19C RAC集群)
数据库·oracle·11g升级19c·xtts跨版本迁移和升级
z1234567898613 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
程序猿DD13 小时前
一个 API Key,统一调用大模型、生图和联网搜索
数据库·网关
TlSfoward14 小时前
抓包代理链路下的 TLS 指纹变化分析 TLSFOWARD抓包工具
数据库·爬虫·网络协议·搜索引擎·php
数智化管理手记14 小时前
账龄分析手工统计易遗漏?自动账龄分析工具怎么搭建
大数据·网络·数据库·人工智能·数据挖掘
LabVIEW开发15 小时前
NI Package Manager安装LabVIEW时InternalServerError错误
网络·数据库·labview·labview知识·labview功能·labview程序
SelectDB15 小时前
PostgreSQL 实时同步到 Apache Doris:一站式 CDC 解决方案
数据库