Redis 有序集合类型

934 阅读10分钟

在集合类型的基础上,有序集合类型(sorted set)为集合中的每个元素都关联了一个分值。有序集合类型和列表类型的元素都是有序的,但是二者存在不同:

  • 列表类型是通过链表实现的,获取靠近两端的数据时速度极快,而当元素数量增多后,访问中间部分数据的速度会较慢,所以它更适合实现很少访问中间元素的应用。
  • 有序集合类型是使用哈希表和跳表(skip list)实现的,所以即使读取位于中间部分的数据速度也很快,时间复杂度是 O(logN)。
  • 在列表中不能简单地调整某个元素的位置,但是在有序集合中可以(通过更改这个元素的分数)。
  • 有序集合比列表类型更耗费内存。

ZADD

通过使用 ZADD 命令,可以向有序集合添加一个或多个新成员。

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

在默认情况下,ZADD 命令将返回成功添加的新成员数量作为返回值,若添加的成员已经存在且分值不同,ZADD 命令会更新该成员的分值,并返回 0(因为这是更新操作)。

分值可以是整数或浮点数,另外,可以使用 -inf 表示无穷小,使用 +inf 表示无穷大。

ZADD 还可以指定选项:

  • XX 选项只会对有序集合已有的成员进行更新,而不会向有序集合添加任何新成员。
  • NX 选项只会向有序集合添加新成员,而不会对已有的成员进行任何更新。
  • CH 选项可以让 ZADD 命令返回被修改(changed)成员的数量作为返回值,被修改包括插入新元素和更新分值。
  • INCR 选项可以让 ZADD 命令对于已存在的成员的分值不是执行覆盖操作,而是增量操作,若指定的分值是负数则是减量操作。此时 ZADD 命令等同于 ZINCRBY 命令

ZREM

通过 ZREM 命令,可以从有序集合中移除指定的一个或多个成员以及与这些成员相关联的分值。

ZREM key member [member...]

ZREM 命令会返回被移除成员的数量作为返回值。如果给定的某个成员并不存在于有序集合中将自动忽略该成员。

ZSCORE

通过 ZSCORE 命令可以获取与给定成员相关联的分值,若给定的有序集合并不存在,或者有序集合中并未包含给定的成员,将返回 nil。

ZSCORE key member

ZINCRBY

通过 ZINCRBY 命令可以对有序集合中指定成员的分值执行自增操作,为其加上指定的增量(可以指定负的值实现减量效果)。

ZINCRBY key increment member

ZINCRBY 命令在执行完自增操作之后,将返回给定成员当前的分值。若给定的有序集合不存在或者成员不存在,此命令就和 ZADD 效果一样。

ZCARD

通过 ZCARD 命令可以取得有序集合的成员数量。如果给定的有序集合并不存在,那么 ZCARD 命令将返回 0 作为结果。

ZCARD key

此命令的时间复杂度是 O(1)。

ZRANK

通过 ZRANK 命令可以取得给定成员的升序排列排名,即成员在按照分值从小到大进行排列时的排名。

ZRANK key member

若指定的 key 或者 member 不存在将返回 nil。

ZREVRANK

与 ZRANK 不同的是,ZREVRANK 命令返回的则是成员的降序排列排名,即成员在按照分值从大到小进行排列时的排名。

ZRANGE

ZRANGE 命令用于获取按照分值大小实施升序排列的成员。其中 start 和 end 指定排好序后的索引范围(闭区间)。

ZRANGE key start end [WITHSCORES]

例如:

redis> zadd list 20 a 30 b 10 c 15 d
(integer) 4
redis> zrange list 0 2
1) "c"
2) "d"
3) "a"
redis> zrange list -1 -1
1) "b"

若希望返回每个成员的分值,可以指定可选参数 WITHSCORES。

ZREVRANGE

和 ZRANGE 的区别是,ZREVRANGE 命令则用于获取按照分值大小实施降序排列的成员。

ZRANGEBYSCORE & ZREVRANGEBYSCORE

通过 ZRANGEBYSCORE 命令或者 ZREVRANGEBYSCORE 命令,可以以升序排列或者降序排列的方式获取有序集合中分值介于指定范围内的成员。

ZRANGEBYSCORE key min max [limit offset count] [WITHSCORES] ZREVRANGEBYSCORE key max min [limit offset count] [WITHSCORES]

命令的 min 参数和 max 参数分别用于指定想要获取的成员的最小分值和最大分值。

例如:

redis> zadd list 20 a 30 b 10 c 15 d
(integer) 4
redis> zrangebyscore list 10 15
1) "c"
2) "d"

若希望返回每个成员的分值,可以指定可选参数 WITHSCORES。

ZRANGEBYSCORE 命令和 ZREVRANGEBYSCORE 命令默认返回全部成员,可以通过 limit 限制返回的成员数量。其中 offset 参数用于指定命令在返回结果之前需要跳过的成员数量,而 count 参数则用于指示命令最多可以返回多少个成员。

在默认情况下,ZRANGEBYSCORE 命令和 ZREVRANGEBYSCORE 命令接受的分值范围都是闭区间分值范围,可以在分值前添加 “(” 代表开区间。另外可以使用 -inf 和 +inf 表示无穷小和无穷大。

redis> zadd list 20 a 30 b 10 c 15 d
(integer) 4
redis> zrangebyscore list (10 15
1) "d"
redis> zrangebyscore list -inf +inf
1) "c"
2) "d"
3) "a"
4) "b"

ZCOUNT

通过 ZCOUNT 命令,可以统计出有序集合中分值介于指定分值范围之内的成员数量。

ZCOUNT key min max

此命令也接受 “(”、-inf、+inf 等字符。

ZREMRANGEBYRANK

ZREMRANGEBYRANK 命令可以从升序排列的有序集合中移除位于指定排名范围内的成员(闭区间),然后返回被移除成员的数量。

ZREMRANGEBYRANK key start end

ZREMRANGEBYSCORE

ZREMRANGEBYSCORE 命令可以从有序集合中移除位于指定分值范围内的成员,并在移除操作执行完毕返回被移除成员的数量。

ZREMRANGEBYRANK key min max

此命令的使用可以参考 ZRANGEBYSCORE。

ZRANGEBYLEX & ZREVRANGEBYLEX

对于拥有不同分值的有序集合成员来说,成员的大小由分值决定,至于分值相同的成员,它们的大小则由该成员在字典序中的大小决定。这种排列规则的一个特例是,当有序集合的所有成员都拥有相同的分值时,有序集合的成员将不再根据分值进行排序,而是根据字典序进行排序。这种情况下,ZRANGEBYSCORE、ZCOUNT 和 ZREMRANGEBYSCORE 等命令都将不再适用。

ZRANGEBYLEX 命令可以从字典序排列的有序集合中获取位于字典序指定范围内的成员,而 ZREVRANGEBYLEX 命令是逆序版的 ZRANGEBYLEX 命令,它会以逆字典序的方式返回指定范围内的成员。

ZRANGEBYLEX key min max [limit offset count[]

ZREVRANGEBYLEX key min max [limit offset count[]

命令的 min 参数和 max 参数用于指定用户想要获取的字典序范围,它们可以是以下 4 种值之一:

  • 带有 "[" 符号的值表示在结果中包含与给定值具有同等字典序大小的成员。
  • 带有 "(" 符号的值表示在结果中不包含与给定值具有同等字典序大小的成员。
  • 加号 "+" 表示无穷大。
  • 减号 "-" 表示无穷小。

例如:

redis> zadd set 0 apple 0 after 0 address 0 book 0 banana 0 clear 0 change
(integer) 7
# 获取全部
redis> zrangebylex set - +
1) "address"
2) "after"
3) "apple"
4) "banana"
5) "book"
6) "change"
7) "clear"
# 获取开头是 a 的
redis> zrangebylex set [a (b
1) "address"
2) "after"
3) "apple"
# 获取开头是 a,b 的
redis> zrangebylex set [a (c
1) "address"
2) "after"
3) "apple"
4) "banana"
5) "book"

ZLEXCOUNT

对于按照字典序排列的有序集合,可以使用 ZLEXCOUNT 命令统计有序集合中位于字典序指定范围内的成员数量。

ZLEXCOUNT key min max

ZLEXCOUNT 命令的 min 参数和 max 参数的格式与 ZRANGEBYLEX 命令接受的 min 参数和 max 参数的格式完全相同。

例如:

redis> zlexcount set [a (b
(integer) 3
redis> zlexcount set [a (c
(integer) 5
redis> zlexcount set [a +
(integer) 7

ZREMRANGEBYLEX

对于按照字典序排列的有序集合,可以使用 ZREMRANGEBYLEX 命令去移除有序集合中位于字典序指定范围内的成员。

ZREMRANGEBYLEX key min max

ZREMRANGEBYLEX 命令在移除指定的成员之后,将返回被移除成员的数量作为命令的返回值。

ZUNIONSTORE & ZINTERSTORE

与集合一样,Redis 也为有序集合提供了相应的并集运算命令 ZUNIONSTORE 和交集运算命令 ZINTERSTORE,这两个命令的基本格式如下

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

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

其中,命令的 numkeys 参数用于指定参与计算的有序集合数量。ZUNIONSTORE 命令和 ZINTERSTORE 命令都会返回计算结果包含的成员数量作为返回值。

默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和。可以显示的使用 AGGREGATE 选项指定聚会函数,可选的聚合函数是 SUM、MAX、MIN。

在默认情况下,ZUNIONSTORE 和 ZINTERSTORE 将直接使用给定有序集合的成员分值去计算结果有序集合的成员分值,也可以通过可选的 WEIGHTS 参数为各个给定有序集合的成员分值设置权重。

在使用 WEIGHTS 选项时,需要为每个给定的有序集合分别设置一个权重,命令会将这个权重与成员的分值相乘,得出成员的新分值,然后执行聚合计算;如果不想改变某个给定有序集合的分值,那么只需要将那个有序集合的权重设置为 1 即可。

redis> zadd list1 10 a 15 b 20 c
(integer) 3
redis> zadd list2 5 a 30 d
(integer) 2
redis> zunionstore list3 2 list1 list2 WEIGHTS 1 2 AGGREGATE MAX
(integer) 4
redis> zrangebyscore list3 -inf +inf WITHSCORES
1) "a"
2) "10"
3) "b"
4) "15"
5) "c"
6) "20"
7) "d"
8) "60"

ZPOPMAX & ZPOPMIN

ZPOPMAX 和 ZPOPMIN 分别用于移除并返回有序集合中分值最大和最小的 N 个元素。

ZPOPMAX|ZPOPMIN key [count]

其中被移除元素的数量可以通过可选的 count 参数来指定。如果没有显式地给定 count 参数,那么命令默认只会移除一个元素。

BZPOPMAX & BZPOPMIN

BZPOPMAX 命令和 BZPOPMIN 命令分别是 ZPOPMAX 命令以及 ZPOPMIN 命令的阻塞版本,这两个阻塞命令都接受任意多个有序集合和一个秒级精度的超时时限作为参数。

BZPOPMAX|BZPOPMIN key [key...] timeout

BZPOPMAX 命令和 BZPOPMIN 命令会依次检查给定的有序集合,并从它遇到的第一个非空有序集合中弹出指定的元素。如果命令在检查了所有给定有序集合之后都没有发现可弹出的元素,那么它将阻塞执行命令的客户端,并在给定的时限之内等待可弹出的元素出现,直到等待时间超过给定时限为止。可以通过将超时时限设置为 0 来让命令一直阻塞,直到可弹出的元素出现为止。

BZPOPMAX 命令和 BZPOPMIN 命令在成功弹出元素时将返回一个包含 3 个项的列表,这 3 个项分别为被弹出元素所在的有序集合、被弹出元素的成员以及被弹出元素的分值。

实战场景

排序

比如希望文章按照修改时间或访问量排序,那么就可以通过有序集合存储每个文章 ID,分值代表访问量或修改时间,按照不同的排序规则取出 ID 后再取出对应的文章信息即可。

排行榜

借助 ZADD、ZINCRBY、ZRANGE 等命令可以简单实现一个排行榜。