# 书接上回
上一篇我们学习的 set 集合这一数据类型。其内部是由 inset 和 hashtable 这种两种数据结构编码的。
如果不记得了,那就来坐穿梭机回去看看吧。 开始穿梭
接下来,我们继续学习一个新的数据类型,有序集合. zset .
# zset 简介
zset , 中文名字叫 有序集合。序这个字,在 Redis 的实现是 score 字段。我们先不急这个字段,后面会介绍。
在 Redis 中有序的数据类型,还有一个就是我们前面学习的 list 了。 它们还都可以获得某一定范围内的元素。
而 zset 的优点是: list 通过链表实现,在两端操作数据都很方便。但是操作中间的数据就比较慢了。 zset 是用 hashtable 和 skiplist 来实现的。即使是操作中间数据,速度也很快。时间复杂度为: O(logN)
zset 的缺点就是:就是比较耗费内存。
# zset 类型的应用场景
- 存储学生成绩快速做成绩排名功能。
- 排行榜,比如:列出某用户当前的全球排名,比赛中胜场数排名。
- 带权重的消息队列功能
# zset 的基本命令
# zadd
- 语法
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
- 解释
将 member 添加有序集合中.
如果 member 存在,会更新 member 的 score 值。
-
NX表示存在相同的member就会设置失败,NX 的作用就是 新增member,不会修改Member -
XX表示不存在相同的member就会设置失败。所以:XX总是更新元素。不会新增元素 -
CH(change): 返回修改的元素个数。更改的元素是添加的新元素以及已为其更新分数的现有元素。因此,命令行中指定的具有与过去相同分数的元素将不计算在内。注意:通常,ZADD的返回值仅计算添加的新元素的数量。 -
INCR: 指定此选项后,ZADD的行为类似于ZINCRBY。在此模式下只能指定一对得分元素。 -
演示
1 | # 设置一个元素 |
# zscore
- 语法
ZSCORE key member
- 解释
zset score
查看对应元素的 score 值
* 当`key`不存在或者`member`不存在的时候,返回`(nil)`
返回 score 的值。
- 演示
1 | # 验证k68不存在的时候,返回nil |
# zincrby
- 语法
ZINCRBY key increment member
- 解释
zset increment by
* increment : 步长。
* member : 指定的成员
为有序集合 key 的成员 member 的 score 值加上 increment 。
如果 key 或者 member 不存在,则新增一个元素。相当于 zadd .
- 演示
1 | # 插入一个不存在的key。 |
# zcard
- 语法
ZCARD key
- 解释
返回 有序集合的 key 中的元素个数。即 member 的个数。
不存在的时候,返回 0 .
- 演示
1 | 返回member的个数 |
# zcount
- 语法
ZCOUNT key min max
- 解释
返回 score 值在 min 和 max 之间的元素的个数。包括等于 min 和 max
- 演示
1 | 127.0.0.1:6379> ZADD k71 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrange
- 语法
ZRANGE key start stop [WITHSCORES]
- 解释
这个命令我们已经用过,就是返回指定开始结束位置上的元素。从 0 开始。
- 演示
1 | 127.0.0.1:6379> ZRANGE k72 1 3 |
# zrevrange
- 语法
ZREVRANGE key start stop [WITHSCORES]
- 解释
返回有序集合中指定区间的成员。
其中成员的位置按 score 值递减 (从大到小) 来排列。 具有相同 score 值的成员按字典序的逆序 ( reverse lexicographical order ) 排列。
- 演示
1 | 127.0.0.1:6379> ZADD k73 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrangebyscore
- 语法
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
- 解释
zset range by score
类似 zrange , 不过是按照 score 的值进行排序的。
[LIMIT offset count] , 是从 offset 开始,返回 count 个。
- 演示
1 | # 返回 score值在 [9,10]之间的member。 |
# zrevrangebyscore
- 语法
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
- 解释
返回有序集 key 中, score 值介于 max 和 min 之间 (默认包括等于 max 或 min ) 的所有的成员。有序集成员按 score 值递减 (从大到小) 的次序排列。
注意各个参数的位置哦。这里和 zrevrange 的参数不一样。
- 演示
1 | 127.0.0.1:6379> ZADD k75 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrank
- 语法
ZRANK key member
- 解释
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增 (从小到大) 顺序排列
- 演示
1 | 127.0.0.1:6379> ZADD k76 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrevrank
- 语法
ZREVRANK key member
- 解释
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减 (从大到小) 排序。
排名以 0 为底,也就是说, score 值最大的成员排名为 0 。
- 演示
1 | 127.0.0.1:6379> ZADD k77 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrem
- 语法
ZREM key member [member ...]
- 解释
zset remove
移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。
当 key 存在但不是有序集类型时,返回一个错误。
- 演示
1 | 127.0.0.1:6379> ZADD k78 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zremrangebyrank
- 语法
ZREMRANGEBYRANK key start stop
- 解释
移除有序集 key 中,指定排名 ( rank ) 区间内的所有成员。
区间分别以下标参数 start 和 stop 指出,包含 start 和 stop 在内。
- 演示
1 | 127.0.0.1:6379> ZADD k79 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zremrangebyscore
- 语法
ZREMRANGEBYSCORE key min max
- 解释
移除有序集 key 中,所有 score 值介于 min 和 max 之间 (包括等于 min 或 max ) 的成员。
- 演示
1 | 127.0.0.1:6379> ZADD k80 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zrangebylex
- 语法
ZRANGEBYLEX key min max [LIMIT offset count]
- 解释
当有序集合的所有成员都具有相同的分值时, 有序集合的元素会根据成员的字典序( lexicographical ordering )来进行排序, 而这个命令则可以返回给定的有序集合键 key 中, 值介于 min 和 max 之间的成员。
注意:
合法的 min 和 max 参数必须包含 ( 或者 [ , 其中 ( 表示开区间(指定的值不会被包含在范围之内), 而 [ 则表示闭区间(指定的值会被包含在范围之内)。
特殊值 + 和 - 在 min 参数以及 max 参数中具有特殊的意义, 其中 + 表示正无限, 而 - 表示负无限。 因此, 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX <zset> - + , 命令将返回有序集合中的所有元素
lex:
表示如果score相等,则按照member的字典顺序排序。
此外这个命令,比如ZRANGBYSCORE稍微强大一点儿。可以指定区间范围,当只知道member,不知道 score 的时候,可以是使用带有lex的命令。
- 演示
1 | 127.0.0.1:6379> zadd k81 1 a 2 b 3 c 4 d 5 f 6 g |
# zlexcount
- 语法
ZLEXCOUNT key min max
- 解释
对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会返回该集合中, 成员介于 min 和 max 范围内的元素数量。
- 演示
1 | 127.0.0.1:6379> zadd k82 1 a 2 b 3 c 4 d 5 f 6 g |
# zremrangebylex
- 语法
ZREMRANGEBYLEX key min max
- 解释
对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会移除该集合中, 成员介于 min 和 max 范围内的所有元素。
- 演示
1 | 127.0.0.1:6379> zadd k83 1 a 2 b 3 c 4 d 5 f 6 g |
# zscan
- 语法
ZSCAN key cursor [MATCH pattern] [COUNT count]
- 解释
这是一个查询命令。 同 SCAN 命令。可以参考这篇文章 010 - 其他命令
SCAN 命令是一个基于游标的迭代器( cursor based iterator ): SCAN 命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。
- 演示
1 | 127.0.0.1:6379> ZADD k84 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10 |
# zunionstore
- 语法
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
-
解释
计算给定的一个或多个有序集合的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集 (结果集) 储存到 destination 。
如果 key 相同的时候,对应的 score 值会相加。WEIGHTS: 使用WEIGHTS选项,你可以为 每个 给定有序集 分别 指定一个乘法因子 (multiplication factor),每个给定有序集的所有成员的score值在传递给聚合函数 (aggregation function) 之前都要先乘以该有序集的因子。AGGREGATE: 使用AGGREGATE选项,你可以指定并集的结果集的聚合方式。
默认使用的参数SUM,可以将所有集合中某个成员的score值之 和 作为结果集中该成员的score值;使用参数MIN,可以将所有集合中某个成员的 最小score值作为结果集中该成员的score值;而参数MAX则是将所有集合中某个成员的 最大score值作为结果集中该成员的score值。
-
演示
1 | 127.0.0.1:6379> ZADD k85_1 1 m1 2 m2 3 m3 |
# zinterstore
- 语法
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight [weight …]] [AGGREGATE SUM|MIN|MAX]
- 解释
计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集 (结果集) 储存到 destination 。
默认情况下,结果集中某个成员的 score 值是所有给定集下该成员 score 值之和.
- 演示
1 | 127.0.0.1:6379> zadd k86_1 1 m1 2 m2 3 m3 4 m4 |
# zset 的内部结构
这里我们主要看 skiplist ,如果忘记了 hashtable ,就看着这篇文章
我们从命令 zadd 入手,找到 zset 的 add 通用方法 zaddGenericCommand(c,ZADD_NONE); 来看一下。省略了部分代码。
1 | ... |
来看一下 createZsetObject() 方法的实现,就再清晰不过了。
1 | /// 创建 Zset 对象。 |
到这里,就是我们和 zset 这种数据类型的初次深入见面了。 我们先看下 zset 这种结构体的定义。
1 | /// 有序集合的结构定义 |
dict 前面已经看过了,这里来看下 zskiplist 。
# skiplist
1 | /// 跳跃表 |
# 总结
- 本章是一个新的常用数据类型,
ZSET有序集合。底层的数据结构是使用的是skipList和hashtable. 关于Skiplist的初步了解文章穿梭机 和Redis中skiplist的实现源码解读穿梭机 - 然后简单介绍了
Redis ZSET数据类型的基础使用场景。关键字有有序,排名,权重等. ZSET的20个常有命令。 后面我会针对这20个命令的实现进行简单的分享.- 然后简单的看了一下
Redis中的数据结构的实现,还是那句话,Redis的数据结构是动态编码的,ZSET是有hashtable和skiplist实现的。 skiplist 是一个非常高效的数据结构,增删查的效率都是O(logN). 实现原理可以参考这篇文章直通车,里面有几种流行的语言的实现,可以针对自己擅长的语言进行查看。
# 最后
期望与你一起遇见更好的自己
