Redis 中的有序集合是一种强大的数据结构,它结合了集合和有序列表的特性。它允许您存储唯一的元素集合,同时为每个元素分配一个分数或排名。该分数用于确定集合中元素的顺序,这使得有序集合成为需要有序数据的应用的绝佳选择。
在 Redis 中,有序集合是通过哈希表和跳跃列表数据结构的组合来实现的。哈希表根据元素的值提供快速访问,而跳跃列表根据元素的分数维护元素的排序顺序。这种双重结构使 Redis 能够有效地执行有序集合上的操作。
有序集合的关键特性之一是能够动态地添加、删除或更新元素,同时保持其排序顺序。您可以将元素插入有序集合中并为其关联一个分数,该分数可以是浮点数。Redis 使用分数来确定元素在有序集合中的位置。如果具有相同值的元素已经存在,则会相应地更新其分数。
有序集合提供了各种操作来操纵数据。您可以根据元素的分数检索特定范围内的元素,从而实现高效的分页或排名功能。Redis 支持包含和不包含的范围查询,允许您按分数或在有序集合中的位置获取元素。此外,您还可以在有序集合上执行集合操作,如并集、交集和差集,从而组合或比较多个有序集合。
Redis 还提供了有效的方法来增加或减少有序集合中元素的分数。此功能在需要跟踪排名或维护排行榜的场景中特别有用。通过增加或减少元素的分数,您可以轻松更新其在有序集合中的位置,而无需进行复杂的操作。
Redis 中的有序集合提供了多项优势。得益于底层哈希表结构,它们提供了基于元素值的快速访问。跳跃列表确保了基于分数的有效排序和范围查询。有序集合广泛用于各种应用,包括排行榜、实时分析、作业调度等。
让我们看一下表 3.9 中一些常用的 ZSET 命令。表 3.9 一些常用的 ZSET 命令
命令 | 示例用法和描述 |
---|---|
ZADD | ZADD key-name score member [score member …] — 将具有给定分数的成员添加到 ZSET |
ZREM | ZREM key-name member [member …] — 从 ZSET 中移除成员,返回被移除的成员数量 |
ZCARD | ZCARD key-name — 返回 ZSET 中的成员数量 |
ZINCRBY | ZINCRBY key-name increment member — 增加 ZSET 中成员的分数 |
ZCOUNT | ZCOUNT key-name min max — 返回分数介于给定最小值和最大值之间的成员数量 |
ZRANK | ZRANK key-name member — 返回给定成员在 ZSET 中的位置 |
ZSCORE | ZSCORE key-name member — 返回成员在 ZSET 中的分数 |
ZRANGE | ZRANGE key-name start stop [WITHSCORES] — 返回排名介于 start 和 stop 之间的成员,可选地包括分数 |
我们在第 1 章和第 2 章中使用过其中一些命令,因此您应该已经很熟悉它们了。让我们快速回顾一下其中一些命令的用法。列表 3.9 显示 Redis 中一些常用 ZSET 命令的示例交互>>> conn.zadd(‘zset-key’, ‘a’, 3, ‘b’, 2, ‘c’, 1) 3
在 Python 中向 ZSET 添加成员的参数顺序与标准 Redis 相反,这使得顺序与 HASH 相同。>>> conn.zcard(‘zset-key’) 3
了解 ZSET 的大小在某些情况下可以告诉我们是否需要修剪我们的 ZSET。>>> conn.zincrby(‘zset-key’, ‘c’, 3) 4.0
我们也可以像对 STRING 和 HASH 值那样增加成员的分数。>>> conn.zscore(‘zset-key’, ‘b’) 2.0
如果我们一直维护计数器或排行榜,获取单个成员的分数可能会很有用。>>> conn.zrank(‘zset-key’, ‘c’) 2
通过获取成员的 0 索引位置,我们以后可以轻松使用 ZRANGE 获取值的范围。>>> conn.zcount(‘zset-key’, 0, 3) 2L
计算具有给定分数范围的项目的数量对于某些任务非常有用。>>> conn.zrem(‘zset-key’, ‘b’) True
删除成员就像添加成员一样容易。>>> conn.zrange(‘zset-key’, 0, -1, withscores=True) [(‘a’, 3.0), (‘c’, 4.0)]
为了调试,我们通常使用此 ZRANGE 调用获取整个 ZSET,但在实际用例中,通常会一次获取相对少量的一组项目。
您可能还记得我们在第 1 章和第 2 章中使用了 ZADD、 ZREM、 ZINCRBY、 ZSCORE 和 ZRANGE 命令,因此它们的语义应该不足为奇。 ZCOUNT 命令与其他命令略有不同,主要用于让您发现分数介于给定最小值和最大值之间的值的数量。
表 3.10 显示了 Redis 中您会觉得有用的更多 ZSET 命令。表 3.10 用于从 ZSET 中获取和删除数据范围以及提供类似 SET 的交集的命令
命令 | 示例用法和描述 |
---|---|
ZREVRANK | ZREVRANK key-name member — 返回成员在 ZSET 中的位置,成员按降序排列 |
ZREVRANGE | ZREVRANGE key-name start stop [WITHSCORES] — 按排名从 ZSET 中获取给定成员,成员按降序排列 |
ZRANGEBYSCORE | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] — 获取 min 和 max 之间的成员 |
ZREVRANGEBYSCORE | ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] — 按降序获取 min 和 max 之间的成员 |
ZREMRANGEBYRANK | ZREMRANGEBYRANK key-name start stop — 移除排名介于 start 和 stop 之间的 ZSET 中的项目 |
ZREMRANGEBYSCORE | ZREMRANGEBYSCORE key-name min max — 移除分数介于 min 和 max 之间的 ZSET 中的项目 |
ZINTERSTORE | ZINTERSTORE dest-key key-count key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] — 对提供的 ZSET 执行类似 SET 的交集操作 |
ZUNIONSTORE | ZUNIONSTORE dest-key key-count key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX] — 对提供的 ZSET 执行类似 SET 的并集操作 |
这是您第一次看到其中一些命令。如果某些 ZREV* 命令让您感到困惑,请记住它们的工作原理与其非反向对应的命令相同,只是 ZSET 的行为如同它是反向顺序(分数从高到低排序)。您可以在下面的列表中看到它们的一些用法示例。列表 3.10 显示 ZINTERSTORE 和 ZUNIONSTORE 的示例交互>>> conn.zadd(‘zset-1’, ‘a’, 1, ‘b’, 2, ‘c’, 3) 3 >>> conn.zadd(‘zset-2’, ‘b’, 4, ‘c’, 1, ‘d’, 0) 3
我们将首先创建几个 ZSET。>>> conn.zinterstore(‘zset-i’, [‘zset-1’, ‘zset-2’]) 2L >>> conn.zrange(‘zset-i’, 0, -1, withscores=True) [(‘c’, 4.0), (‘b’, 6.0)]
执行 ZINTERSTORE 或 ZUNIONSTORE 时,我们的默认聚合方式是求和,因此位于多个 ZSET 中的项目的分数会被相加。
虽然我们仅限于求和 (sum)、最小值 (min) 和最大值 (max),但提供不同的聚合方式很容易。>>> conn.zunionstore(‘zset-u’, [‘zset-1’, ‘zset-2′], aggregate=’min’) 4L >>> conn.zrange(‘zset-u’, 0, -1, withscores=True) [(‘d’, 0.0), (‘a’, 1.0), (‘c’, 1.0), (‘b’, 2.0)]
我们也可以将 SET 作为输入传递给 ZINTERSTORE 和 ZUNIONSTORE;它们的行为就像所有分数都等于 1 的 ZSET 一样。>>> conn.sadd(‘set-1’, ‘a’, ‘d’) 2 >>> conn.zunionstore(‘zset-u2’, [‘zset-1’, ‘zset-2’, ‘set-1’]) 4L >>> conn.zrange(‘zset-u2’, 0, -1, withscores=True) [(‘d’, 1.0), (‘a’, 2.0), (‘c’, 4.0), (‘b’, 6.0)]
ZSET 的并集和交集乍一看可能难以理解,所以我们来看一些图示,展示交集和并集过程中发生的情况。图 3.1 显示了两个 ZSET 的交集以及最终的 ZSET 结果。在本例中,我们的聚合方式是默认的求和 (sum),因此分数被相加。
与交集不同,当我们执行并集操作时,至少存在于一个输入 ZSET 中的项目会包含在输出中。图 3.2 显示了使用不同聚合函数 min 执行并集操作的结果,如果在多个输入 ZSET 中存在同一成员,则取其最小分数。
在第 1 章中,我们利用了可以将 SET 作为 ZSET 并集和交集操作的一部分的特性。此功能使我们能够轻松地从群组中添加和移除文章,而无需将分数和插入时间传播到额外的 ZSET 中。图 3.3 显示了 ZUNIONSTORE 调用,它将两个 ZSET 和一个 SET 组合生成最终的 ZSET。
在第 7 章中,我们将使用 ZINTERSTORE 和 ZUNIONSTORE 作为几种不同类型搜索的一部分。我们还将讨论几种不同的方法,如何将 ZSET 分数与可选的 WEIGHTS 参数结合,以进一步扩展可以使用 SET 和 ZSET 解决的问题类型。
在开发应用程序时,您可能遇到过一种称为发布/订阅的模式,也称为 pub/sub。Redis 包含了这项功能,我们接下来将进行介绍。
2 分数在 Redis 内部实际存储为 IEEE 754 双精度浮点数。