【Redis初阶】Zset 有序集合

Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~

🌱🌱个人主页:奋斗的明志

🌱🌱所属专栏:Redis
📚本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。

Zset 有序集合

前言

Zset有序集合相对于字符串、列表、哈希、集合来说会有一些陌生。它保留了set集合不能有重复成员的特点,但与set集合不同的是,Zset有序集合中的每个元素都有一个唯一的浮点类型的分数(score)与之关联,着使得Zset有序集合中的元素是可以维护有序性的,但这个有序不是用下标,作为排序依据而是用这个分数。如图所示,该有序集合显示了三国中的武将的武力。(这里的有序指升序/降序)(排序规则按照一定的分数)

  • 有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利用有序集合,可

    以帮助我们在实际开发中解决很多问题。

  • 有序集合中的元素是不能重复的(唯一的),但分数允许重复。类比于一次考试之后,每个人一定有一个唯一的分数,但分数允许相同。

列表、集合、有序集合三者的异同点:

一、普通命令

1.ZADD

  • 添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负极限也是合法的。

  • ZADD 的相关选项:

  • XX:仅仅用于更新已经存在的元素,不会添加新元素。(仅在member成员已存在时才更新分数。)

  • NX:仅用于添加新元素,不会更新已经存在的元素。(仅在member成员不存在时才添加)

  • LT:表示只有当member成员 当前的分数 小于 新分数时,才会更新分数。

    换句话说,只有在新的分数大于现有分数时,才会进行更新,否则不会对该成员进行任何修改。

  • GT:表示只有当member成员 当前的分数 大于 新分数时,才会更新分数。

    换句话说,只由在新的分数小于现有分数时,才会进行更新,否则不会对该成员进行任何修改。

  • CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。

  • INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。

  • 语法:

sql 复制代码
ZADD key [NX | XX] [GT | LT] [CH] [INCR] score member [score member...]
  • 添加元素的时候,既要添加 score 也要添加 member ,score和member称为是一个"pair"
  • 不要把 score 和 member 理解成"键值对"(key-value pair)
  • 对于有序集合来说,既可以通过 score 找到对应的 member ,也能通过 member 找到对应的 score
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(log(N)) n是有序集合中已经有的元素个数,要求新增的元素,要放到合适的位置上(之所以log(N) 不是 N ,也是充分利用了有序这样的特点,当然zset 内部的数据结构,主要是调表)
  • 返回值:本次添加成功的元素个数。
  • 示例:
sql 复制代码
#分数如果相同的话,按照元素自身字符串的字典来排序
#zset有序集合内部 元素是按照升序来排列的
 127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> ZADD key 99 吕布 98 赵云 96 典韦 95 关羽
(integer) 4
#zrange 查看有序集合中元素的详情
#类似于 lrange 可以指定一对下标构成的区间
#有序集合,本身元素就有先后顺序的,因此就可以给这个有序集合赋予下标这样的概念
127.0.0.1:6379> ZRANGE key 0 -1
1) "\xe5\x85\xb3\xe7\xbe\xbd"
2) "\xe5\x85\xb8\xe9\x9f\xa6"
3) "\xe8\xb5\xb5\xe4\xba\x91"
4) "\xe5\x90\x95\xe5\xb8\x83"
# redis内部在存储数据的时候,是按照二进制方式进行存储的,这就意味着redis服务器不负责"字符编码"的
#要把二进制数据转化为 汉字,还需要客户端来进行支持~~
#redis-cli --raw 这样的命令进行处理
#ctrl + c 退出redis客户端
root@iZ2ze1wowvpkkp7wnart6oZ:~# redis-cli  --raw
127.0.0.1:6379> zrange key 0 -1
关羽
典韦
赵云
吕布
sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
关羽
95
典韦
96
赵云
98
吕布
99
sql 复制代码
127.0.0.1:6379> zadd key 97 赵云
0
127.0.0.1:6379> zrange key 0 -1
关羽
典韦
赵云
吕布
127.0.0.1:6379> zrange key 0 -1 withscores
关羽
95
典韦
96
赵云
97
吕布
99
sql 复制代码
127.0.0.1:6379> zadd key 10 吕布
0
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
关羽
95
典韦
96
赵云
97
  • nx xx 的使用
sql 复制代码
127.0.0.1:6379> zadd key nx 94 张飞
1
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
张飞
94
关羽
95
典韦
96
赵云
97
127.0.0.1:6379> zadd key xx 92 张飞
0
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
张飞
92
关羽
95
典韦
96
赵云
97

2.ZCARD

  • 获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数。
  • 语法:
sql 复制代码
ZCARD key
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(1)
  • 返回值:zset 内的元素个数
  • 示例:
sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
张飞
92
关羽
95
典韦
96
赵云
97
127.0.0.1:6379> zcard key
5

3.ZCOUNT

  • 返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
  • 语法:
sql 复制代码
ZCOUNT key min max
  • 命令有效版本:2.0.0 之后
  • 时间复杂度:O(log(N)) 先根据min找到对应的元素,再根据max找到对应的元素。实际上Zset内部,会记录每个元素当前的"排行/次序"。查询到元素,就知道了元素所在的"次序",就可以直接把 max 对应的元素次序和 min 对应的元素的次序,减去即可!!!(不是遍历) (min 和 max 可以写成浮点数)(在浮点数中,存在两个特殊的数值:inf 无穷大 -inf 负无穷大)
  • 返回值:满足条件的元素列表个数。
  • 示例:
sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
张飞
92
关羽
95
典韦
96
赵云
97
127.0.0.1:6379> zcount key 95 97
3
#去除95
127.0.0.1:6379> zcount key (95 97
2
#去除95 97
127.0.0.1:6379> zcount key (95 (97
1

4.ZRANGE

  • 返回指定区间里的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。
  • 语法:
sql 复制代码
ZRANGE key start stop [WITHSCORES]
#此处的 [start, stop] 为下标构成的区间. 从 0 开始, ⽀持负数.
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(log(N)+M) n指整个有序集合的个数,m为区间的个数
  • 返回值:区间内的元素列表。
  • 示例:
sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
张飞
92
关羽
95
典韦
96
赵云
97

5.ZREVRANGE

  • 返回指定区间里的元素,分数按照降序。(逆序)带上 WITHSCORES 可以把分数也返回。
  • 备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
  • 语法:
sql 复制代码
ZREVRANGE key start stop [WITHSCORES]
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(log(N)+M)
  • 返回值:区间内的元素列表。
  • 示例:
sql 复制代码
127.0.0.1:6379> zrevrange key 0 -1 withscores
赵云
97
典韦
96
关羽
95
张飞
92
吕布
10

6.ZRANGEBYSCORE

  • 返回分数在 min 和 max 之间的元素(根据分数),默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
  • 备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
  • 语法:
sql 复制代码
ZRANGEBYSCORE key min max [WITHSCORES]
  • 命令有效版本:1.0.5 之后
  • 时间复杂度:O(log(N)+M)
  • 返回值:区间内的元素列表。
  • 示例:
sql 复制代码
127.0.0.1:6379> ZRANGEBYSCORE key 95 97 withscores
关羽
95
典韦
96
赵云
97

7.ZPOPMAX

  • 删除并返回分数最⾼的 count 个元素。
  • 语法:
sql 复制代码
ZPOPMAX key [count]
  • 命令有效版本:5.0.0 之后
  • 时间复杂度:O(log(N) * M) n有序集合的元素个数,m count 要删除的元素个数
  • 返回值:分数和元素列表。
  • 示例:
sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
张飞
92
关羽
95
典韦
96
赵云
97
127.0.0.1:6379> ZPOPMAX key
赵云
97
127.0.0.1:6379> ZPOPMAX key 2
典韦
96
关羽
95

如果存在多个元素,分数相同,同时为最大值,zpopmax删除的时候,仍然只删除其中一个元素!!!

sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
吕布
10
关羽
95
典韦
96
张飞
96
127.0.0.1:6379> zpopmax key
张飞
96

8.BZPOPMAX

  • ZPOPMAX 的阻塞版本。
  • 咱们这里的"有序集合"也可以视为是一个"优先级队列",有的时候,也需要一个带有"阻塞功能"的优先级队列
  • 阻塞也是在有序集合为空的时候触发的
  • 语法:
sql 复制代码
BZPOPMAX key [key ...] timeout
  • 命令有效版本:5.0.0 之后
  • 时间复杂度:O(log(N))
  • 返回值:元素列表。
  • 示例:

9.ZPOPMIN

  • 删除并返回分数最低的 count 个元素。
  • 语法:
sql 复制代码
ZPOPMIN key [count]
  • 命令有效版本:5.0.0 之后
  • 时间复杂度:O(log(N) * M)
  • 返回值:分数和元素列表。
  • 示例:
sql 复制代码
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu
3
127.0.0.1:6379> zrange key 0 -1 withscores
zhangsan
10
lisi
20
wangwu
30
127.0.0.1:6379> zpopmin key
zhangsan
10

10.BZPOPMIN

  • ZPOPMIN 的阻塞版本。

  • 语法:

sql 复制代码
BZPOPMIN key [key ...] timeout
  • 命令有效版本:5.0.0 之后
  • 时间复杂度:O(log(N))
  • 返回值:元素列表。
  • 示例:

11.ZRANK

  • 返回指定元素的排名,升序。
  • 语法:
sql 复制代码
ZRANK key member
  • 命令有效版本:2.0.0 之后
  • 时间复杂度:O(log(N))
  • 返回值:排名。
  • 示例:
sql 复制代码
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> zadd key 10 zhangsan 20 lisi 30 wangwu 40 zhaoliu
4
127.0.0.1:6379> zrank key lisi
1
127.0.0.1:6379> zrank key zhaoliu
3

12.ZREVRANK

  • 返回指定元素的排名,降序。
  • 语法:
sql 复制代码
ZREVRANK key member
  • 命令有效版本:2.0.0 之后
  • 时间复杂度:O(log(N))
  • 返回值:排名。(下标)

13.ZSCORE

  • 返回指定元素的分数。
  • 语法:
sql 复制代码
ZSCORE key member
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(1)
  • 返回值:分数。
  • 示例:
sql 复制代码
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZSCORE myzset "one"
"1

14.ZREM

  • 删除指定的元素。
  • 语法:
sql 复制代码
ZREM key member [member ...]
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(M*log(N))
  • 返回值:本次操作删除的元素个数。
  • 示例:
sql 复制代码
127.0.0.1:6379> zrange key 0 -1 withscores
zhangsan
10
lisi
20
wangwu
30
zhaoliu
40
127.0.0.1:6379> zrem key lisi
1
127.0.0.1:6379> zrange key 0 -1 withscores
zhangsan
10
wangwu
30
zhaoliu
40

15.ZREMRANGEBYRANK

  • 按照排序,升序删除指定范围的元素,左闭右闭。
  • 语法:
sql 复制代码
ZREMRANGEBYRANK key start stop
  • 命令有效版本:2.0.0 之后
  • 时间复杂度:O(log(N)+M)
  • 返回值:本次操作删除的元素个数。
  • 示例:
sql 复制代码
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
"three"
"3"

16.ZREMRANGEBYSCORE

  • 按照分数删除指定范围的元素,左闭右闭。
  • 语法:
sql 复制代码
ZREMRANGEBYSCORE key min max
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(log(N)+M)
  • 返回值:本次操作删除的元素个数。
  • 示例:
sql 复制代码
redis> ZADD myzset 1 "one"
(integer) 1
redis> ZADD myzset 2 "two"
(integer) 1
redis> ZADD myzset 3 "three"
(integer) 1
redis> ZREMRANGEBYSCORE myzset -inf (2
(integer) 1
redis> ZRANGE myzset 0 -1 WITHSCORES
"two"
"2"
"three"
"3"

17.ZINCRBY

  • 为指定的元素的关联分数添加指定的分数值。
  • 语法:
sql 复制代码
ZINCRBY key increment member
  • 命令有效版本:1.2.0 之后
  • 时间复杂度:O(log(N))
  • 返回值:增加后元素的分数。
  • 示例:
sql 复制代码
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
"two"
"2"
"one"
"3"

二、集合间操作

1.有序集合的并集操作

2.ZUNIONSTORE

  • 求出给定有序集合中元素的并集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。
  • 语法:
sql 复制代码
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
  • 命令有效版本:2.0.0 之后
  • 时间复杂度:O(N)+O(M*log(M)) N 是输入的有序集合总的元素个数; M 是最终结果的有序集合的元素个数.
  • 返回值:目标集合中的元素个数
sql 复制代码
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
redis> ZUNIONSTORE out 2 zset1 zset2 WEIGHTS 2 3
(integer) 3
redis> ZRANGE out 0 -1 WITHSCORES
"one"
"5"
"three"
"9"
"two"
"10"

3.有序集合的交集操作

4.ZINTERSTORE

  • 求出给定有序集合中元素的交集并保存进目标有序集合中,在合并过程中以元素为单位进行合并,元素对应的分数按照不同的聚合方式和权重得到新的分数。
  • 语法:
sql 复制代码
ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]][AGGREGATE <SUM | MIN | MAX>]
  • 命令有效版本:2.0.0 之后
  • 时间复杂度:O(NK)+O(Mlog(M)) N 是输入的有序集合中, 最小的有序集合的元素个数; K 是输入了几个有序集合; M 是最终结果的有序集合的元素个数.
  • 返回值:目标集合中的元素个数
  • 示例:
sql 复制代码
root@iZ2ze1wowvpkkp7wnart6oZ:~# redis-cli --raw
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> zadd key1 10 zhangsan 20 lisi 30 wangwu
3
127.0.0.1:6379> zadd key2 1 zhangsan 2 lisi 3 zhaoliu
3
127.0.0.1:6379> ZINTERSTORE key3 2 key1 key2
2
127.0.0.1:6379> zrange key3 0 -1 withscores
zhangsan
11
lisi
22
127.0.0.1:6379> ZINTERSTORE key4 2 key1 key2 weights 2 3
2
127.0.0.1:6379> zrange key4 0 -1 withscores
zhangsan
23
lisi
46
127.0.0.1:6379> ZINTERSTORE key5 2 key1 key2 aggregate max
2
127.0.0.1:6379> zrange key5 0 -1 withscores
zhangsan
10
lisi
20
127.0.0.1:6379> ZINTERSTORE key6 2 key1 key2 aggregate min
2
127.0.0.1:6379> zrange key6 0 -1 withscores
zhangsan
1
lisi
2

三、命令小结

四、内部编码

有序集合类型的内部编码有两种:

  • ziplist(压缩列表):当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个),同时每个元素的值都小于zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会用ziplist 来作为有序集合的内部实现,ziplist 可以有效减少内存的使用。
  • skiplist(跳表):当 ziplist 条件不满足时,有序集合会使用skiplist 作为内部实现,因为此时ziplist 的操作效率会下降。
  • 1)当元素个数较少且每个元素较小时,内部编码为 ziplist:
sql 复制代码
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:
sql 复制代码
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:
sql 复制代码
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 功能:

sql 复制代码
zadd user:ranking:2022-03-15 3 james

之后如果再获得赞,可以使用 zincrby:

sql 复制代码
zincrby user:ranking:2022-03-15 1 james

2.取消用户赞数

由于各种原因(例如用户注销、用户作弊等)需要将用户删除,此时需要将用户从榜单中删除掉,可以使⽤ zrem。例如删除成员tom:

sql 复制代码
zrem user:ranking:2022-03-15 tom

3.展示获取赞数最多的 10 个用户

此功能使⽤ zrevrange 命令实现:

sql 复制代码
zrevrangebyrank user:ranking:2022-03-15 0 9

4.展示用户信息以及用户分数

次功能将用户名作为键后缀,将用户信息保存在哈希类型中,至于用户的分数和排名可以使用zscore和 zrank 来实现。

sql 复制代码
hgetall user:info:tom
zscore user:ranking:2022-03-15 mike
zrank user:ranking:2022-03-15 mike
相关推荐
IT项目管理1 小时前
达梦数据库DMHS介绍及安装部署
linux·数据库
你都会上树?1 小时前
MySQL MVCC 详解
数据库·mysql
大春儿的试验田1 小时前
高并发收藏功能设计:Redis异步同步与定时补偿机制详解
java·数据库·redis·学习·缓存
likeGhee1 小时前
python缓存装饰器实现方案
开发语言·python·缓存
hqxstudying1 小时前
Redis为什么是单线程
java·redis
C182981825751 小时前
OOM电商系统订单缓存泄漏,这是泄漏还是溢出
java·spring·缓存
Ein hübscher Kerl.2 小时前
虚拟机上安装 MariaDB 及依赖包
数据库·mariadb
醇醛酸醚酮酯2 小时前
Qt项目锻炼——TODO清单(二)
开发语言·数据库·qt
GreatSQL社区3 小时前
用systemd管理GreatSQL服务详解
数据库·mysql·greatsql
掘根3 小时前
【MySQL进阶】错误日志,二进制日志,mysql系统库
数据库·mysql