Redis 有序集合对象
2023-3-24
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序集合的元素值,一个是排序值。
有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。
 
有序集合的编码可以是 ziplist 或者 skiplist 。
ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。
压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。
如果执行以下 ZADD 命令, 那么服务器将创建一个有序集合对象作为 price 键的值:
如果 price 键的值对象使用的是 ziplist 编码, 那么这个值对象:
notion image
 
而对象所使用的压缩列表:
notion image
 
skiplist 编码的有序集合对象使用 zset 结构作为底层实现, 一个 zset 结构同时包含一个字典和一个跳跃表:
zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素, 每个跳跃表节点都保存了一个集合元素: 跳跃表节点的 object 属性保存了元素的成员, 而跳跃表节点的 score 属性则保存了元素的分值。 通过这个跳跃表, 程序可以对有序集合进行范围型操作, 比如 ZRANK 、ZRANGE 等命令就是基于跳跃表 API 来实现的。
 
除此之外, zset结构中的dict字典为有序集合创建了一个从成员到分值的映射, 字典中的每个键值对都保存了一个集合元素: 字典的键保存了元素的成员, 而字典的值则保存了元素的分值。 通过这个字典, 程序可以用复杂度查找给定成员的分值, ZSCORE 命令就是根据这一特性实现的, 而很多其他有序集合命令都在实现的内部用到了这一特性。
有序集合每个元素的成员都是一个字符串对象, 而每个元素的分值都是一个 double 类型的浮点数。 值得一提的是, 虽然 zset 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值, 所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值, 也不会因此而浪费额外的内存。
 
为什么有序集合需要同时使用跳跃表和字典来实现?
在理论上来说, 有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现, 但无论单独使用字典还是跳跃表, 在性能上对比起同时使用字典和跳跃表都会有所降低。
如果我们只使用字典来实现有序集合, 那么虽然以内存空间 (因为要创建一个数组来保存排序后的元素)。另一方面, 如果我们只使用跳跃表来实现有序集合, 那么跳跃表执行范围型操作的所有优点都会被保留, 但因为没有了字典, 所以根据成员查找分值这一操作的复杂度将从
因为以上原因, 为了让有序集合的查找和范围型操作都尽可能快地执行, Redis 选择了同时使用字典和跳跃表两种数据结构来实现有序集合。
如果前面price键创建的不是ziplist编码的有序集合对象, 而是skiplist编码的有序集合对象, 那么这个有序集合对象将会是:
notion image
而对象所使用的 zset 结构将会是:
notion image
在实际中, 字典和跳跃表会共享元素的成员和分值, 所以并不会造成任何数据重复, 也不会因此而浪费任何内存。
 

编码的转换

当有序集合对象可以同时满足以下两个条件时, 对象使用 ziplist 编码:
  1. 有序集合保存的元素数量小于 128 个;
  1. 有序集合保存的所有元素成员的长度都小于 64 字节;
不能满足以上两个条件的有序集合对象将使用 skiplist 编码。在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了
 
以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 zset-max-ziplist-entries 选项和 zset-max-ziplist-value 选项的说明。
对于使用 ziplist 编码的有序集合对象来说, 当使用 ziplist 编码所需的两个条件中的任意一个不能被满足时, 程序就会执行编码转换操作, 将原本储存在压缩列表里面的所有集合元素转移到 zset 结构里面, 并将对象的编码从 ziplist 改为 skiplist 。
 
 

Redis有序集合命令

有序集合键的值为有序集合对象, 用于有序集合键的所有命令都是针对有序集合对象来构建的:
notion image
 
  • ZADD:向有序集合添加一个或多个成员,或者更新已存在成员的分数
  • ZCARD:获取有序集合的成员数
  • ZCOUNT:计算在有序集合中指定区间分数的成员数
  • ZINCRBY:有序集合中对指定成员的分数加上增量 increment
  • ZINTERSTORE:计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中
  • ZLEXCOUNT:在有序集合中计算指定字典区间内成员数量
  • ZRANGE:通过索引区间返回有序集合成指定区间内的成员
  • ZRANGEBYLEX :通过字典区间返回有序集合的成员
  • ZRANGEBYSCORE:通过分数返回有序集合指定区间内的成员
  • ZRANK:返回有序集合中指定成员的索引
  • ZREM:移除有序集合中的一个或多个成员
  • ZREMRANGEBYLEX:移除有序集合中给定的字典区间的所有成员
  • ZREMRANGEBYRANK:移除有序集合中给定的排名区间的所有成员
  • ZREMRANGEBYSCORE:移除有序集合中给定的分数区间的所有成员
  • ZREVRANGE:返回有序集中指定区间内的成员,通过索引,分数从高到底
  • ZREVRANGEBYSCORE:返回有序集中指定分数区间内的成员,分数从高到低排序
  • ZREVRANK:返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
  • ZSCORE:返回有序集中,成员的分数值
  • ZUNIONSTORE:计算一个或多个有序集的并集,并存储在新的 key 中
  • ZSCAN:迭代有序集合中的元素(包括元素成员和元素分值)
 
 

应用场景

Zset 类型(Sorted Set,有序集合) 可以根据元素的权重来排序,我们可以自己来决定每个元素的权重值。比如说,可以根据元素插入 Sorted Set 的时间确定权重值,先插入的元素权重小,后插入的元素权重大。
在面对需要展示最新列表、排行榜等场景时,如果数据更新频繁或者需要分页显示,可以优先考虑使用 Sorted Set。

排行榜

有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。
我们以博文点赞排名为例,小林发表了五篇博文,分别获得赞为 200、40、100、50、150。
文章 arcticle:4 新增一个赞,可以使用 ZINCRBY 命令(为有序集合key中元素member的分值加上increment):
查看某篇文章的赞数,可以使用 ZSCORE 命令(返回有序集合key中元素个数):
获取小林文章赞数最多的 3 篇文章,可以使用 ZREVRANGE 命令(倒序获取有序集合 key 从start下标到stop下标的元素):
获取小林 100 赞到 200 赞的文章,可以使用 ZRANGEBYSCORE 命令(返回有序集合中指定分数区间内的成员,分数由低到高排序):
 

电话、姓名排序

使用有序集合的 ZRANGEBYLEX 或 ZREVRANGEBYLEX 可以帮助我们实现电话号码或姓名的排序,以 ZRANGEBYLEX (返回指定成员区间内的成员,按 key 正序排列,分数必须相同)为例。
注意:不要在分数不一致的 SortSet 集合中去使用 ZRANGEBYLEX和 ZREVRANGEBYLEX 指令,因为获取的结果会不准确。
电话排序
可以将电话号码存储到 SortSet 中,然后根据需要来获取号段:
获取所有号码:
获取 132 号段的号码:
获取132、133号段的号码:
 
姓名排序
获取所有人的名字:
获取名字中大写字母A开头的所有人:
获取名字中大写字母 C 到 Z 的所有人:
  • Redis
  • Redis 集合对象二进制数组 bitmap
    目录