1.zset类型基本介绍
有序描述的是:升序/降序
Set 集合
1.唯一
2. 无序
孙行者,行者孙, 者行孙 =>同一只猴~~
List
有序的
孙行者,行者孙, 者行孙 =>不同的猴~~
zset 中的 member 仍然要求是唯一的!!(score 则可以重复)
排序的规则是啥?
给 zset 中的 member 同时引入了一个属性分数(score),浮点类型
每个 member 都会安排一个分数
进行排序的时候, 就是依照此处的 分数 大小来进行升序/降序排序
2.zset相关命令
2.1 zadd
添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。
ZADD 的相关选项:
• XX:仅仅⽤于更新已经存在的元素,不会添加新元素。• NX:仅⽤于添加新元素,不会更新已经存在的元素。• CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。• INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。
语法:
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member ...]
命令有效版本:1.2.0 之后时间复杂度:O(log(N))返回值:本次添加成功的元素个数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 1 "uno"(integer) 1redis> ZADD myzset 2 "two" 3 "three"(integer) 2redis> ZRANGE myzset 0 -1 WITHSCORES1) "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) 0redis> ZRANGE myzset 0 -1 WITHSCORES1) "uno"2) "1"3) "one"4) "10"5) "two"6) "20"7) "three"8) "30"redis> ZADD myzset CH 100 one 200 two 300 three(integer) 3redis> ZRANGE myzset 0 -1 WITHSCORES1) "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) 0redis> ZRANGE myzset 0 -1 WITHSCORES1) "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) 2redis> ZRANGE myzset 0 -1 WITHSCORES1) "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 WITHSCORES1) "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) 2redis> ZRANGE myzset 0 -1 WITHSCORES1) "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"
2.2 ZRANGE
返回指定区间⾥的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。
语法:
ZRANGE key start stop [WITHSCORES]
此处的 [start, stop] 为下标构成的区间. 从 0 开始, ⽀持负数.命令有效版本:1.2.0 之后时间复杂度:O(log(N)+M)返回值:区间内的元素列表。
⽰例
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZRANGE myzset 0 -1 WITHSCORES1) "one"2) "1"3) "two"4) "2"5) "three"6) "3"redis> ZRANGE myzset 0 -11) "one"2) "two"3) "three"redis> ZRANGE myzset 2 31) "three"redis> ZRANGE myzset -2 -11) "two"2) "three"
- 想要包含分数加withscores
- 修改操作
- nx
- 添加不存在的元素成功了
- 此时的修改操作失败了
- xx
- 修改成功了
- 由于有xx修改没有的元素失败了
2.3 zcard
获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数。
语法:
ZCARD key
命令有效版本:1.2.0 之后时间复杂度:O(1)返回值:zset 内的元素个数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZCARD myzset(integer) 2
2.4 zcount
返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
语法:
ZCOUNT key min max
命令有效版本:2.0.0 之后时间复杂度:O(log(N))返回值:满⾜条件的元素列表个数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZCOUNT myzset -inf +inf(integer) 3redis> ZCOUNT myzset 1 3(integer) 3redis> ZCOUNT myzset (1 3(integer) 2redis> ZCOUNT myzset (1 (3(integer) 1
2.5 ZREVRANGE(按照降序)
返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。
备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
语法:ZREVRANGE key start stop [WITHSCORES]命令有效版本:1.2.0 之后时间复杂度:O(log(N)+M)返回值:区间内的元素列表。⽰例:redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZREVRANGE myzset 0 -1 WITHSCORES1) "three"2) "3"3) "two"4) "2"5) "one"6) "1"redis> ZREVRANGE myzset 0 -11) "three"2) "two"3) "one"redis> ZREVRANGE myzset 2 31) "one"redis> ZREVRANGE myzset -2 -11) "two"2) "one"
2.6 ZRANGEBYSCORE
返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
语法:
ZRANGEBYSCORE key min max [WITHSCORES]
命令有效版本:1.0.5 之后时间复杂度:O(log(N)+M)返回值:区间内的元素列表。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZRANGEBYSCORE myzset -inf +inf1) "one"2) "two"3) "three"redis> ZRANGEBYSCORE myzset 1 21) "one"2) "two"redis> ZRANGEBYSCORE myzset (1 21) "two"redis> ZRANGEBYSCORE myzset (1 (2(empty array)
2.7 zpopmax
删除并返回分数最⾼的 count 个元素。
可以解决top-问题
语法:
ZPOPMAX key [count]
命令有效版本:5.0.0 之后时间复杂度:O(log(N) * M)N 是有序集合的元素个数.count 要删除的元素个数.
- 此处删除的是 最大值 !!! 有序集合,最大值就相当于最后一个元素(尾删)
- 既然是尾删,为什么我们不把这个最后一个元素的位置特殊记录下来~~ 后续删除不就可以 O(1)了嘛? 省去了查找的过程~~O(logN) =>0(1)
- 这个事情是有可能的!!
- 但是很遗憾,目前 redis 并没有这么做~
- 事实上,redis 的源码中,针对有序集合, 确实是记录了 尾部 这样的特定位置~~但是在实际删除的时候,并没有用上这个特性,而是直接调用了一个"通用的删除函数"(给定一个 member 的值, 进行查找找到位置之后再删除~~)
返回值:分数和元素列表。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZPOPMAX myzset1) "three"2) "3"
2.8 BZPOPMAX
ZPOPMAX 的阻塞版本。
语法:
BZPOPMAX key [key ...] timeout
命令有效版本:5.0.0 之后时间复杂度:O(log(N))返回值:元素列表。
⽰例:
redis> DEL zset1 zset2(integer) 0redis> ZADD zset1 0 a 1 b 2 c(integer) 3redis> BZPOPMAX zset1 zset2 01) "zset1"2) "c"3) "2"
2.9 ZPOPMIN
删除并返回分数最低的 count 个元素。
语法:
ZPOPMIN key [count]
命令有效版本:5.0.0 之后时间复杂度:O(log(N) * M)返回值:分数和元素列表。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZPOPMIN myzset1) "one"2) "1"
2.10 BZPOPMIN
ZPOPMIN 的 阻塞版本 。
语法:
BZPOPMIN key [key ...] timeout
命令有效版本:5.0.0 之后时间复杂度:O(log(N))返回值:元素列表。
⽰例:
redis> DEL zset1 zset2(integer) 0redis> ZADD zset1 0 a 1 b 2 c(integer) 3redis> BZPOPMIN zset1 zset2 01) "zset1"2) "a"3) "0"
2.11 ZRANK
返回指定元素的排名, 升序。
语法:
ZRANK key member
命令有效版本:2.0.0 之后时间复杂度:O(log(N))返回值:排名。⽰例:redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZRANK myzset "three"(integer) 2redis> ZRANK myzset "four"(nil)
2.12 ZREVRANK
返回指定元素的排名, 降序。
语法:
ZREVRANK key member
命令有效版本:2.0.0 之后时间复杂度:O(log(N))返回值:排名。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZREVRANK myzset "one"(integer) 2redis> ZREVRANK myzset "four"(nil)
2.13 ZSCORE
返回指定元素的分数。
语法:
ZSCORE key member
命令有效版本:1.2.0 之后时间复杂度:O(1)返回值:分数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZSCORE myzset "one""1"
2.14 ZREM
删除指定的元素。
语法:
ZREM key member [member ...]
命令有效版本:1.2.0 之后时间复杂度:O(M*log(N))返回值:本次操作删除的元素个数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZREM myzset "two"(integer) 1redis> ZRANGE myzset 0 -1 WITHSCORES1) "one"2) "1"3) "three"4) "3"
2.15 ZREMRANGEBYRANK
按照排序,升序删除指定范围的元素,左闭右闭。//使用这个下标描述范围进行删除
语法:
ZREMRANGEBYRANK key start stop
命令有效版本:2.0.0 之后时间复杂度:O(log(N)+M)返回值:本次操作删除的元素个数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZREMRANGEBYRANK myzset 0 1(integer) 2redis> ZRANGE myzset 0 -1 WITHSCORES1) "three"2) "3"
2.16 ZREMRANGEBYSCORE
按照分数删除指定范围的元素,左闭右闭。
语法:
ZREMRANGEBYSCORE key min max
时间复杂度:O(log(N)+M)返回值:本次操作删除的元素个数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZADD myzset 3 "three"(integer) 1redis> ZREMRANGEBYSCORE myzset -inf (2(integer) 1redis> ZRANGE myzset 0 -1 WITHSCORES1) "two"2) "2"3) "three"4) "3"
2.17 ZINCRBY
为指定的元素的关联分数添加指定的分数值。
- 不光会修改分数内容
- 也能同时移动元素位置保持整个有序集合仍然是升序的
- 也可以针对小数进行相关操作
语法:
ZINCRBY key increment member
命令有效版本:1.2.0 之后时间复杂度:O(log(N))返回值:增加后元素的分数。
⽰例:
redis> ZADD myzset 1 "one"(integer) 1redis> ZADD myzset 2 "two"(integer) 1redis> ZINCRBY myzset 2 "one""3"redis> ZRANGE myzset 0 -1 WITHSCORES1) "two"
3.集合间操作
3.1 ZINTERSTORE
求出给定有序集合中元素的交集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。
语法:
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight[weight ...]] [AGGREGATE <SUM | MIN | MAX>] (以什么规则进行合并,默认求和)
命令有效版本:2.0.0 之后时间复杂度:O(N*K)+O(M*log(M))N 是输⼊的有序集合中, 最⼩的有序集合的元素个数; K 是输⼊了⼏个有序集合; M 是最终结果的有序集合的元素个数.返回值:⽬标集合中的元素个数
⽰例:
redis> ZADD zset1 1 "one"(integer) 1redis> ZADD zset1 2 "two"(integer) 1redis> ZADD zset2 1 "one"(integer) 1redis> ZADD zset2 2 "two"(integer) 1redis> ZADD zset2 3 "three"(integer) 1redis> ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3(integer) 2redis> ZRANGE out 0 -1 WITHSCORES1) "one"2) "5"3) "two"4) "10"
3.2 ZUNIONSTORE
求出给定有序集合中元素的并集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。
语法:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
命令有效版本:2.0.0 之后时间复杂度:O(N)+O(M*log(M)) N 是输⼊的有序集合总的元素个数; M 是最终结果的有序集合的元素个数.返回值:⽬标集合中的元素个数
⽰例:
redis> ZADD zset1 1 "one"(integer) 1redis> ZADD zset1 2 "two"(integer) 1redis> ZADD zset2 1 "one"(integer) 1redis> ZADD zset2 2 "two"(integer) 1redis> ZADD zset2 3 "three"(integer) 1redis> ZUNIONSTORE out 2 zset1 zset2 WEIGHTS 2 3(integer) 3redis> ZRANGE out 0 -1 WITHSCORES1) "one"2) "5"3) "three"4) "9"5) "two"6) "10"
4.命令⼩结
命令 | 时间复杂度 |
zadd key score member [score member ...] |
O(k * log(n)),k 是添加成员的个数,n 是当前有序集合的元素个数
|
zcard key | O(1) |
zscore key member | O(1) |
zrank key member zrevrank key member
| O(log(n)),n 是当前有序集合的元素个数 |
zrem key member [member ...] |
O(k * log(n)),k 是删除成员的个数,n 是当前有序集合的元
素个数
|
zincrby key increment member | O(log(n)),n 是当前有序集合的元素个数 |
zrange key start end [withscores] zrevrange key start end [withscores] |
O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数
|
zcount | O(log(n)),n 是当前有序集合的元素个数 |
zremrangebyrank key start end |
O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数
|
zremrangebyscore key min max |
O(k + log(n)),k 是获取成员的个数,n 是当前有序集合的元素个数
|
zinterstore destination numkeys key [key ...] |
O(n * k) + O(m * log(m)),n 是输⼊的集合最⼩的元素个数, k 是集合个数, m 是⽬标集合元素个数
|
zunionstore destination numkeys key [key
...]
|
O(n) + O(m * log(m)),n 是输⼊集合总元素个数,m 是⽬标集合元素个数
|
5.zset编码方式
有序集合类型的内部编码有两种:
•
ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个), 同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会⽤ ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
•
skiplist(跳表):当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时
ziplist 的操作效率会下降。
- 简单来说, 跳表是一个"复杂链表!
- 查询元素 时间复杂度 logN
- 相比于树形结构,更适合按照范围获取元素~~
6.zset的应用场景
6.1 排行榜系统
最关键的应用场景,排行榜系统
1.微博热搜
2.游戏天梯排行
3. 成绩排行行...
关键要点:
用来排行的"分数"是实时变化的~~
虽然是实时变化,也能够高
效的更新排行~~
使用 zset 来完成上述操作,就非常简单~~
比如游戏天梯排行,
只需要把玩家信息和对应的分数给放到有序集合中即可,
自动就形成了一个排行榜~~
随时可以按照 排行(下标),按照分数 进行范围查询~~
随着分数发生改变,也可以比较方便的,zincrby 修改分数,排行顺序也能自动调整 (logN)zset 是一个选择,不是说,非得用 redis 的 zset~~
有些场景确实可以用到有序集合,又不方便使用redis~~ 可以考虑使用其他方式的有序集合~~