Redis Zset类型
Zset(有序集合)它是集合的一种,不仅可以保存元素,还可以为每个元素关联一个 double 类型的分数(score),Redis 正是通过分数来为集合中的元素进行从小到大的排序。在 Zset 中,元素的值是唯一的,但分数(score)却可以重复。
有序集合:该有序集合显示了三国中的武将的武力
有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利用有序集合,可以帮助我们在实际开发中解决很多问题。
列表、集合、有序集合三者的异同点:
Zset命令
zadd
zadd 用于添加或者更新指定的元素以及关联的分数到 Zset 中,分数应该符合 double 类型,+inf/-inf作为正负极限也是合法的。
语法:
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
key
: 有序集合的键名。score
: 成员的分数。member
: 要添加到有序集合的成员。
相关选项:
XX:仅仅用于更新已经存在的元素,不会添加新元素。
NX:仅用于添加新元素,不会更新已经存在的元素。
CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。
时间复杂度:O(log(N))
返回值:本次添加成功的元素个数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 1 "uno"
(integer) 1
redis> zadd myzset 2 "two" 3 "three"
(integer) 2
redis> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
redis> zadd myzset 10 one 20 two 30 three
(integer) 0
redis> zrange myzset 0 -1 withscores
1) "uno"
2) "1"
3) "one"
4) "10"
5) "two"
6) "20"
7) "three"
8) "30"
#CH选项-包含本次更新的元素的个数
redis> zadd myzset CH 100 one 200 two 300 three
(integer) 3
redis> zrange myzset 0 -1 withscores
1) "uno"
2) "1"
3) "one"
4) "100"
5) "two"
6) "200"
7) "three"
8) "300"
redis> zadd myzset XX 1 one 2 two 3 three 4 four 5 five
(integer) 0
redis> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
redis> zadd myzset NX 100 one 200 two 300 three 400 four 500 five
(integer) 2
redis> zrange myzset 0 -1 withscores
# NX-只添加,不更新,只添加了 four 和 five
1) "one"
2) "1"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
9) "four"
10) "400"
11) "five"
12) "500"
redis> zadd myzset INCR 10 one
"11"
redis> zrange myzset 0 -1 withscores
1) "uno"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
7) "one"
8) "11"
9) "four"
10) "400"
11) "five"
12) "500"
redis> zadd myzset -inf "negative infinity" +inf "positive infinity"
(integer) 2
redis> zrange myzset 0 -1 withscores
# -inf 无穷小 +inf 无穷大
1) "negative infinity"
2) "-inf"
3) "uno"
4) "1"
5) "two"
6) "2"
7) "three"
8) "3"
9) "one"
10) "11"
11) "four"
12) "400"
13) "five"
14) "500"
15) "positive infinity"
16) "inf"
zcard
zcard 用于获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数。
语法:
ZCARD key
时间复杂度:O(1)
返回值:zset 内的元素个数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zcard myzset
(integer) 2
zcount
返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
语法:
ZCOUNT key min max
时间复杂度:O(log(N))
返回值:满⾜条件的元素列表个数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zcount myzset -inf +inf
(integer) 3
redis> zcount myzset 1 3
(integer) 3
# 通过 (表示开区间
redis> zcount myzset (1 3
(integer) 2
redis> zcount myzset (1 (3
(integer) 1
zrange
返回指定区间里的元素,分数按照升序。带上 withscores 可以把分数也返回。
语法:
ZRANGE key start stop [WITHSCORES]
此处的 [start, stop] 为下标构成的区间. 从 0 开始, ⽀持负数.
时间复杂度:O(log(N)+M)
返回值:区间内的元素列表。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
redis> zrange myzset 0 -1
1) "one"
2) "two"
3) "three"
redis> zrange myzset 2 3
1) "three"
redis> zrange myzset -2 -1
1) "two"
2) "three"
zrevrange
返回指定区间里的元素,分数按照降序。带上 withscores 可以把分数也返回。
备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 zrange 中。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zrevrange myzset 0 -1 withscores
1) "three"
2) "3"
3) "two"
4) "2"
5) "one"
6) "1"
redis> zrevrange myzset 0 -1
1) "three"
2) "two"
3) "one"
redis> zrevrange myzset 2 3
1) "one"
redis> zrevrange myzset -2 -1
1) "two"
2) "one"
zrangebyscore
返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
语法:
ZRANGEBYSCORE key min max [WITHSCORES]
时间复杂度:O(log(N)+M)
返回值:区间内的元素列表。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zrangebyscore myzset -inf +inf
1) "one"
2) "two"
3) "three"
redis> zrangebyscore myzset 1 2
1) "one"
2) "two"
redis> zrangebyscore myzset (1 2
1) "two"
redis> zrangebyscore myzset (1 (2
(empty array)
zpopmax
删除并返回分数最高的 count 个元素。
语法:
ZPOPMAX key [count]
时间复杂度:O(log(N) * M)
返回值:分数和元素列表。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zpopmax myzset
1) "three"
2) "3"
bzpopmax
zpopmax 的阻塞版本。它会持续阻塞连接,直到有成员可以被弹出或达到超时时间。
语法:
BZPOPMAX key [key ...] timeout
timeout
: 最大阻塞时间,以秒为单位,表示如果在此时间内没有元素弹出,命令将会超时。
时间复杂度:O(log(N))
返回值:元素列表,如果在超时之前没有可用的成员,命令将返回 null
。
示例:
redis> bzpopmax set1 set2 10 set1 set2 10
这条命令将阻塞等待最多10秒钟,如果 set1
或 set2
中有成员,它将返回其中分数最高的成员。如果超过10秒仍然没有可弹出的成员,命令将超时返回空响应。
zpopmin
删除并返回分数最低的 count 个元素。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zpopmin myzset
1) "one"
2) "1"
bzpopmin
zpopmin 的阻塞版本。
示例:
redis> del zset1 zset2
(integer) 0
redis> zadd zset1 0 a 1 b 2 c
(integer) 3
redis> bzpopmin zset1 zset2 0
1) "zset1"
2) "a"
3) "0"
zrank
返回指定元素的排名,升序。排名有0开始
语法:
ZRANK key member
时间复杂度:O(log(N))
返回值:排名。 没有则,返回nil
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zrank myzset "three"
(integer) 2
redis> zrank myzset "four"
(nil)
zrevrank
返回指定元素的排名,降序。排名有0开始
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zrevrank myzset "one"
(integer) 2
redis> zrevrank myzset "four"
(nil)
zscore
返回指定元素的分数。
语法:
ZSCORE key member
时间复杂度:O(1)
返回值:分数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zscore myzset "one"
"1"
zrem
删除指定的元素。
语法:
ZREM key member [member ...]
时间复杂度:O(M*log(N))
返回值:本次操作删除的元素个数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zrem myzset "two"
(integer) 1
redis> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "three"
4) "3"
zremrangebyrank
按照排序,升序删除指定范围的元素,左闭右闭。
语法:
ZREMRANGEBYRANK key start stop
时间复杂度:O(log(N)+M)
返回值:本次操作删除的元素个数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
redis> zremrangebyrank myzset 0 1
(integer) 2
redis> zrange myzset 0 -1 withscores
1) "three"
2) "3"
zremrangebyscore
按照分数删除指定范围的元素,左闭右闭。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zadd myzset 3 "three"
(integer) 1
# 表示 无穷小到1的范围,不包括2
redis> zremrangebyscore myzset -inf (2
(integer) 1
redis> zrange myzset 0 -1 withscores
1) "two"
2) "2"
3) "three"
4) "3"
zincrby
为指定的元素的关联分数增加或减少指定的分数值。
语法:
ZINCRBY key increment member
- increment 为正数表示增加,负数表示减少
时间复杂度:O(log(N))
返回值:增加后元素的分数。
示例:
redis> zadd myzset 1 "one"
(integer) 1
redis> zadd myzset 2 "two"
(integer) 1
redis> zincrby myzset 2 "one"
"3"
redis> zrange myzset 0 -1 withscores
1) "two"
2) "2"
3) "one"
4) "3"
redis> zincrby myzset -2 "one" # -2 负数表示减少
"1"
redis> zrange myzset 0 -1 withscores
1) "one"
2) "1"
3) "two"
4) "2"
集合间操作
交集
有序集合的交集操作:
zinterstore
zinterstore 求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元 素对应的分数按照不同的聚合方式和权重得到新的分数。
语法:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
destination
:结果将存储在这个键中。numkeys
:要进行交集的输入键(有序集合)的数量。key
:一个有序集合的名称。WEIGHTS
:一个可选的关键字,后面跟着在交集操作中应用于有序集合的权重。AGGREGATE
:一个可选的关键字,指定如何聚合交集元素的分数。可以是SUM
、MIN
或MAX
。
时间复杂度:O(N×K)+O(M×log(M)) N 是输入的有序集合中, 最小的有序集合的元素个数; K 是输入了几个有序集合; M 是最终结果的有序集合的元素个数.
返回值:目标集合中的元素个数
示例:
redis> zadd zset1 1 "one"
(integer) 1
redis> zadd zset1 2 "two"
(integer) 1
redis> zadd zset2 1 "one"
(integer) 1
redis> zadd zset2 2 "two"
(integer) 1
redis> zadd zset2 3 "three"
(integer) 1
# 交集时,zset1 的元素的分数会乘以 2,而 zset2 的元素的分数会乘以 3。
redis> zinterstore out 2 zset1 zset2 weights 2 3
(integer) 2
redis> zrange out 0 -1 withscores
1) "one"
2) "5" # 1×2+1×3=2+3=5
3) "two"
4) "10"
并集
有序集合的并集操作:
zunionstore
zunionstore 求出给定有序集合中元素的并集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元 素对应的分数按照不同的聚合方式和权重得到新的分数。
语法:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
destination
:结果将存储在这个键中,即新的有序集合的名称。numkeys
:要进行并集计算的输入键(有序集合)的数量。key
:一个有序集合的名称。WEIGHTS
:一个可选的关键字,后面跟着在并集操作中应用于有序集合的权重。AGGREGATE
:一个可选的关键字,指定如何聚合并集元素的分数。可以是SUM
、MIN
或MAX
。
时间复杂度:O(N)+O(M*log(M)) N 是输入的有序集合总的元素个数; M 是最终结果的有序集合的元素个数.
返回值:目标集合中的元素个数
示例:
redis> zadd zset1 1 "one"
(integer) 1
redis> zadd zset1 2 "two"
(integer) 1
redis> zadd zset2 1 "one"
(integer) 1
redis> zadd zset2 2 "two"
(integer) 1
redis> zadd zset2 3 "three"
(integer) 1
# zset1 整体 * 2 zset2 整体 * 3
redis> zunionstore out 2 zset1 zset2 weights 2 3
(integer) 3
redis> zrange out 0 -1 withscores
1) "one"
2) "5"
3) "three"
4) "9"
5) "two"
6) "10"
内部编码
有序集合类型的内部编码有两种:
- ziplist(压缩列表):当有序集合的元素个数小于 zset-max-ziplist-entries 配置(默认 128 个), 同时每个元素的值都小于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用 ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
- skiplist(跳表):当 ziplist 条件不满足时,有序集合会使用 skiplist 作为内部实现,因为此时 ziplist 的操作效率会下降。
1)当元素个数较少且每个元素较小时,内部编码为 ziplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3
(integer) 3
127.0.0.1:6379> object encoding zsetkey
"ziplist"
2)当元素个数超过 128 个,内部编码 skiplist:
127.0.0.1:6379> zadd zsetkey 50 e1 60 e2 30 e3 ... 省略 ... 82 e129
(integer) 129
127.0.0.1:6379> object encoding zsetkey
"skiplist"
3)当某个元素⼤于 64 字节时,内部编码 skiplist:
127.0.0.1:6379> zadd zsetkey 50 "one string bigger than 64 bytes ... 省略 ..."
(integer) 1
127.0.0.1:6379> object encoding zsetkey
"skiplist"
使用场景
有序集合比较典型的使⽤场景就是排行榜系统。例如常见的网站上的热榜信息,榜单的维度可能是多方面的:按照时间、按照阅读量、按照点赞量。本例中我们使用点赞数这个维度,维护每天的热榜:
1)添加用户赞数
例如用户 james 发布了一篇文章,并获得3个赞,可以使用有序集合的 zadd 和 zincrby 功能::
zadd user:ranking:2022-03-15 3 james
之后如果再获得赞,可以使用 zincrby:
zincrby user:ranking:2022-03-15 1 james
2)取消用户赞数
由于各种原因(例如用户注销、用户作弊等)需要将用户删除,此时需要将用户从榜单中删除掉,可以使用 zrem。例如删除成员 tom:
zrem user:ranking:2022-03-15 tom
3)展示获取赞数最多的 10 个用户
此功能使用 zrevrange 命令实现:
zrevrangebyrank user:ranking:2022-03-15 0 9
4)展示用户信息以及用户分数
此功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用 zscore 和 zrank 来实现。
hgetall user:info:tom
zscore user:ranking:2022-03-15 mike
zrank user:ranking:2022-03-15 mike