Redis Zset 类型全解析 - 指南

1.引言

在 Redis 常用数据类型中,Zset(有序集合)是兼顾 “元素唯一性” 与 “排序性” 的核心类型 —— 它在 Set “去重” 基础上,为每个元素绑定一个score(分数),通过分数实现 “升序 / 降序排列”。从热搜排行榜、游戏天梯到成绩排名,Zset 凭借高效的排序与查询能力,成为分布式系统中 “有序数据管理” 的关键器具。本文将从核心特性出发,逐条拆解命令、解析底层编码,再结合实际业务场景说明应用逻辑,帮你吃透 Zset 类型的所有关键知识点。

2.Zset 类型的核心特性

“Set 的去重能力” 与 “排序需求” 的结合就是Zset 的设计

  • 元素唯一性:与 Set 一致,Zset 中不会存在重复member(元素),即使重复添加(如zadd zset1 10 a 20 a),最终也只会保留 1 个a,且score以最后一次添加为准;
  • 排序性:每个member对应一个score(浮点类型),Zset 会按score自动排序(默认升序);若score相同,则按member的 “字典序”(二进制存储顺序)排序;
  • 双重查询能力:支持两种查询维度 —— 按score范围查询元素(如 “分数 100-200 的用户”),或按排名(下标)查询元素(如 “前 10 名用户”)。

与 Set、List 的关键差异

类型元素唯一性排序性核心适用场景
Zset唯一按score排序(升 / 降)排行榜、带权重的有序材料
Set唯一无序去重存储、关系计算(交集)
List可重复按插入顺序排序有序数据、消息队列

3.Zset 类型核心命令

3.1 元素添加与基础查询:zadd、zrange

zadd

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

  • 功能:向 Zset 中添加 1 个或多个 “score+member” 对;支持多种可选参数,适配不同业务需求。
  • 关键参数说明:
    • NX:仅当member不存在时添加,存在则忽略;
    • XX:仅当member存在时更新score,不存在则忽略;
    • LT:仅当新score小于原score时更新;
    • GT:仅当新score大于原score时更新;
    • CH:返回值具备 “新增元素个数 + 更新score的元素个数”(默认仅返回新增个数);
    • INCR:将score视为增量(如zadd zset1 INCR 2 a,若a已存在,score+2)。
  • 时间复杂度:O (logN)(N 为 Zset 元素总数,需找到score对应排序位置)。
  • 返回值:默认返回 “新增元素个数”;指定CH时返回 “新增 + 更新元素总数”;指定INCR时返回更新后的score。

zrange

zrange key start stop [WITHSCORES]

  • 机制:按 “升序” 获取 Zset 中[start, stop]排名区间的元素(start=0为第一名,stop=-1为最后一名);指定WITHSCORES时,同时返回元素的score。
  • 时间复杂度:O (logN + M)(N 为 Zset 总数,M 为查询区间的元素个数)。
  • 返回值:默认返回元素列表;指定WITHSCORES时,返回 “元素 1+score1 + 元素 2+score2” 的列表。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
nx:
在这里插入图片描述
xx:
在这里插入图片描述
LT:
在这里插入图片描述
GT:
在这里插入图片描述
CH: 默认情况下zadd返回的是本次新增的元素个数。指定CH后,返回值还涵盖本次更新的元素个数

在这里插入图片描述
INCR:在这里插入图片描述

3.2 元素计数:zcard、zcount

zcard

zcard key

  • 功能:获取 Zset 中的元素总个数(类似 Set 的scard)。
  • 时间复杂度:O (1)(底层维护元素计数器,直接返回)。
  • 返回值:元素个数(Key 不存在时返回 0)。

在这里插入图片描述

zcount

zcount key min max

  • 功能:获取 Zset 中score在[min, max]区间内的元素个数(默认闭区间,可通过(min或max)表示开区间)。
  • 特殊符号:inf表示正无穷(如+inf),-inf表示负无穷(如-inf),可用于 “所有小于 X” 或 “所有大于 Y” 的场景。
  • 时间复杂度:O (logN)(通过score定位区间边界,无需遍历元素)。
  • 返回值:区间内的元素个数

在这里插入图片描述
开区间表示:
96变成开区间

在这里插入图片描述
97 变开区间
在这里插入图片描述
这个设定比较反直觉,不过考虑到兼容性(在其他广泛利用的软件也采用了这样的设定)一改全要改,成本高

min、max可以写成 浮点数。
在浮点数中: inf:无穷大 -inf:负无穷大

在这里插入图片描述

3.3 排序与排名查询:zrevrange、zrangebyscore、zrank、zrevrank、zscore

zrevrange

zrevrange key start stop [WITHSCORES]

  • 功能:按 “降序” 获取 Zset 中[start, stop]排名区间的元素(start=0为score最高的第一名),与zrange逻辑相反。
  • 时间复杂度:O(logN + M)。

在这里插入图片描述

zrangebyscore

zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

  • 效果:按 “升序” 获取score在[min, max]区间内的元素;支持LIMIT分页(offset为起始位置,count为获取个数),机制与zrange互补。
  • 注意:该命令未来可能被zrange合并,建议优先使用zrange。
  • 时间复杂度:O(logN + M)。

在这里插入图片描述

zrank

zrank key member

  • 功能:获取member在 Zset 中的 “升序排名”(rank=0为第一名,即score最小的)。
  • 时间复杂度:O (logN)(基于跳表快速定位元素)。
  • 返回值:排名(member不存在时返回nil)。

在这里插入图片描述

zrevrank

zrevrank key member

  • 功能:获取member在 Zset 中的 “降序排名”(rank=0为第一名,即score最大的),与zrank逻辑相反。

在这里插入图片描述

zscore

zscore key member

  • 功能:获取指定member对应的score。
  • 时间复杂度:O (1)(Redis 对该操作做特殊优化,通过哈希表直接映射member到score,牺牲少量空间换效率)。
  • 返回值:score(浮点型字符串,member不存在时返回nil)。

在这里插入图片描述

3.4 元素删除:zpopmax、bzpopmax、zpopmin、bzpopmin、zrem、zremrangebyrank、zremrangebyscore

zpopmax

zpopmax key [count]

  • 功能:按 “降序” 删除并返回count个score最大的元素(默认count=1,删除 1 个)。
  • 时间复杂度:O (logN * M)(M 为删除的元素个数,每个元素删除需定位)。
  • 返回值:默认返回 “元素 + score” 的列表;count>1时返回多个 “元素 + score” 对。

在这里插入图片描述
score相同,只删一个
在这里插入图片描述
此处删除最大值,在有序集合,也就是尾删。尾删许可把最后一个元素的位置特殊记录下来,后续删除就可以O(1)了。不过redis源码中没有采用,虽然记录了“尾部”,不过还是调用了 通用的删除函数(给定一个member,查找后删除)
存在优化空间,不过优先级不高,一般优化还是先找到瓶颈,再针对性能降序优化。其次log N 在N不是特别特有大的时候,和O(1)是差不多的。

bzpopmax

bzpopmax key [key ...] timeout

  • 功能:zpopmax的阻塞版本 —— 若所有 Key 对应的 Zset 为空,阻塞timeout秒(timeout=0表示永久阻塞);直到任一 Zset 有元素,删除并返回score最大的元素。
  • 时间复杂度:O (logN)(单个元素删除时间)。

在这里插入图片描述
为空阻塞,直到添加元素时返回max

在这里插入图片描述
有元素就会直接返回,直到再次空。

时间复杂度:O(logN) 通用方法删除最大值所用时间。

zpopmin

zpopmin key [count]

  • 功能:按 “升序” 删除并返回count个score最小的元素,与zpopmax逻辑相反。
  • 时间复杂度:O(logN * M)。

在这里插入图片描述
在这里插入图片描述

bzpopmin

bzpopmin key [key ...] timeout

  • 效果:zpopmin的阻塞版本,逻辑与bzpopmax一致,仅删除score最小的元素。
  • 时间复杂度:O(logN)。

在这里插入图片描述

zremrangebyrank

zremrangebyrank key start stop

  • 特性:按 “升序排名” 删除[start, stop]区间内的元素(start=0为第一名,stop=-1为所有元素)。
  • 时间复杂度:O (logN + M)(M 为删除的元素个数)。
  • 返回值:成功删除的元素个数。

在这里插入图片描述

zremrangebyscore

zremrangebyscore key min max

  • 特性:按score范围删除[min, max]区间内的元素(默认闭区间,支持(min或max)表示开区间,-inf/+inf表示负无穷 / 正无穷)。
  • 时间复杂度:O (logN + M)(定位score边界为 O (logN),删除元素为 O (M))。
  • 返回值:成功删除的元素个数。

在这里插入图片描述

3.5 元素分数修改:zincrby

zincrby key increment member

  • 功能:对 Zset 中指定member的score进行 “增量修改”(increment为正数时score增加,为负数时score减少);若member不存在,会自动创建并将score设为increment。
  • 关键特性:修改score后,Zset 会自动重新排序,保证元素仍按score升序排列。
  • 时间复杂度:O (logN)(修改后需重新定位元素位置)。

在这里插入图片描述
不光修改分数内容,也会移动元素位置保证有序集合是升序的。

在这里插入图片描述
小数:
在这里插入图片描述

3.6 集合间运算:zinterstore、zunionstore

这类命令用于计算多个 Zset 的 “交集”“并集”,并将结果存储到新 Zset 中,拥护权重设置与分数聚合规则,是 Zset 处理 “多维度有序数据” 的核心应用。

zinterstore

zinterstore destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

  • 能力:计算numkeys个 Zset 的 “交集”(仅保留所有 Zset 中都存在的member),将结果存储到destination中;若destination已存在,会先清空原有元素。
  • 关键参数说明:
    • numkeys:指定参与交集运算的 Zset 个数(必须为正整数);
    • WEIGHTS:为每个 Zset 设置 “权重系数”,计算时member的score会乘以对应权重(默认权重为 1);
    • AGGREGATE:指定交集后member的score聚合规则(SUM:分数求和,默认;MIN:取最小分数;MAX:取最大分数)。
  • 时间复杂度:O(NK)+O(Mlog(M))
  • 返回值:交集结果的元素个数。

在这里插入图片描述

N:最小的集合的元素个数
K:集合的个数
M:最终结果的集合元素的个数

化简:K一般不多,近似1。N和M认为是同一数量级(近似)==》O(M*log(M))

默认就是相加
在这里插入图片描述
带权:
在这里插入图片描述
改变运算方式:取最大
在这里插入图片描述
取最小
在这里插入图片描述

zunionstore

zunionstore destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

  • 功能:计算numkeys个 Zset 的 “并集”(保留所有 Zset 中存在的member,自动去重),将结果存储到destination中;参数逻辑与zinterstore一致,仅运算规则不同(并集 vs 交集)。
  • 返回值:并集结果的元素个数。

默认运算方式是求和
在这里插入图片描述
带权重:

在这里插入图片描述
最大/小值:
在这里插入图片描述

3.7 小结

在这里插入图片描述
在这里插入图片描述

4.Zset 类型的底层编码

  • 假设有序集合中元素个数少/单个元素体积小 : ziplist (压缩列表,节省空间)(6.2.0版本后引入了listpack,替代ziplist)
  • 如果有序集合中元素个数多/单个元素体积大 : skiplist(跳表)

在这里插入图片描述
跳表:复杂链表

跳表上查询元素的时间复杂度:O(logN)
类似二叉搜索树,相比于树形结构,更适合按照范围获取元素。

跳表(skiplist)的核心原理

Zset 的skiplist编码是其高效排序的关键,本质是 “多层有序链表”,经过 “索引层” 减少查询时的遍历次数:

  1. 结构组成:跳表具备 “底层有序链表” 和多层 “索引链表”—— 底层链表存储所有member(按score升序),索引层仅存储部分member,高层索引的 “跨度” 大于低层索引;
  2. 查询逻辑:从最高层索引开始,按score比较,若当前元素的score小于目标值,跳至下一个元素;若大于目标值,降至下一层索引继续查询,直到底层链表找到目标元素;
  3. 效率优势:跳表的查询、插入、删除时间复杂度均为 O (logN),且相比红黑树(同样 O (logN)),跳表的 “范围查询”(如zrange)更高效(无需中序遍历,直接遍历底层链表区间)。

轻松点说就是有多层索引,然后越高级的索引表中,索引的数量越少,从最高级的索引开始遍历,如果是升序的话,比索引大继续遍历,比索引小就找到一个范围,然后根据这个范围在下一层索引里找,一直锁定到原始数据的范围,再在这个范围中遍历。这样到达logN ,类似二分,不过不是按照中分的,是范围分。

5. Zset 类型的核心应用场景

5.1 排行榜架构(微博、热搜、游戏天梯、成绩…)

比如游戏的排行,只需要把玩家信息和对应的分数放到有序集合中即可。随着分数发生变化,使用zincrby修改,排行榜的顺序也会自动调整。

像排行榜此种使用到的内存并不大,假设4字节存userid 8字节存score。一个玩家也就12字节,一亿玩家–》12亿字节 差不多是1.2GB。
像王者荣耀的战力系统,胜场战力,挑战战力,巅峰系数等待,允许通过ziterstore/zunionstore提供的加权重的方式处理。
再比如热点事件,浏览量、点赞量、转发量、评论量 ,这些权重怎么分配,也可以提供weight去设置。

zset是一个选择,倘若有些场景可以用到有序集合,又不方便使用redis,可以考虑其他方式的有序集合。

5.2 带权重的消息队列

传统消息队列(如 List 实现)无法按 “优先级” 处理消息,而 Zset 可通过score设置消息优先级(score越大优先级越高),达成 “高优先级消息优先消费”,适用于 “订单处理(VIP 订单优先)”“告警通知(紧急告警优先)” 等场景。

5.3 业务视角

选择是否用 Zset,需判断业务需求是否符合以下核心特征:

  • 需求 1:数据需按指定指标排序(如排行榜、优先级队列)——Zset 按score自动排序,无需额外维护排序逻辑;
  • 需求 2:需按范围快速查询 / 统计(如分数区间、时间范围)——zrangebyscore/zcount命令协助 O (logN)级别的范围操作;
  • 需求 3:需多维度数据聚合(如多标签匹配、多指标评分)——zinterstore/zunionstore协助权重聚合,灵活计算交集 / 并集。

6.小结

Redis Zset 类型的核心价值在于 “有序性 + 高效性”—— 它在 Set “去重” 基础上,通过score实现自动排序,同时借助跳表底层结构,保证增删查及范围操作的高效性,是分布式系统中 “有序数据管理” 的核心工具。
建议:

  1. 合理设计score:score不仅是排序依据,还可结合业务需求承载 “优先级”“时间戳”“权重” 等信息(如用时间戳实现时间范围查询),提升 Zset 的复用性;
  2. 控制 Zset 大小:单个 Zset 的元素个数建议不超过 10 万,超过后采用 “分桶存储”(如按用户 ID 哈希分桶、按时间分桶),避免跳表层级过深导致性能下降;
  3. 优先用zrevrange替代zrange:排行榜、优先级队列等场景多需 “降序” 展示(如分数最高在前),zrevrange可直接返回降序结果,无需额外处理。
posted @ 2025-11-04 10:34  ycfenxi  阅读(4)  评论(0)    收藏  举报