上一篇我们学习的 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
。在此模式下只能指定一对得分元素。
演示
## 设置一个元素
127.0.0.1:6379> ZADD k67 1 m1
(integer) 1
## 设置多个元素
127.0.0.1:6379> ZADD k67 2 m2 3 m3 4 m4 5 m5
(integer) 4
## 演示 NX 语义,只能新增.
127.0.0.1:6379> ZADD k67 NX 5 m5
(integer) 0
127.0.0.1:6379> ZADD k67 NX 6 m6
(integer) 1
127.0.0.1:6379> ZADD k67 NX 6 m6
(integer) 0
## 演示 XX 语言,只能修改
127.0.0.1:6379> ZADD k67 XX 7 m7
(integer) 0
127.0.0.1:6379> ZADD k67 7 m7
(integer) 1
## 进行修改,注意返回值. 如果要返回个数,则加 CH
127.0.0.1:6379> ZADD k67 XX 7 m7
(integer) 0
127.0.0.1:6379> ZADD k67 XX 77 m7
(integer) 0
## 演示CH, 返回修改的个数
127.0.0.1:6379> ZADD k67 CH 8 m8 9 m9 10 m10
(integer) 3
127.0.0.1:6379> ZADD k67 CH 8 m8 999 m9 10 m10
(integer) 1
## 演示INCR, 增长
127.0.0.1:6379> ZADD k67 11 m11
(integer) 1
## 此时 score 表示的是步长
127.0.0.1:6379> ZADD k67 INCR 10 m11
"21"
## 查看设置的值。
127.0.0.1:6379> ZRANGE k67 0 -1 WITHSCORES
1) "m1"
2) "1"
3) "m2"
4) "2"
5) "m3"
6) "3"
7) "m4"
8) "4"
9) "m5"
10) "5"
11) "m6"
12) "6"
13) "m8"
14) "8"
15) "m10"
16) "10"
17) "m11"
18) "21"
19) "m7"
20) "77"
21) "m9"
22) "999"
zscore
ZSCORE key member
zset score
查看对应元素的score
值
* 当`key`不存在或者`member`不存在的时候,返回`(nil)`
返回score
的值。
# 验证k68不存在的时候,返回nil
127.0.0.1:6379> ZSCORE k68 m1
(nil)
127.0.0.1:6379> ZADD k68 1 m1
(integer) 1
# 返回元素的score值
127.0.0.1:6379> ZSCORE k68 m1
"1"
# 验证 member 不存在的时候,返回
127.0.0.1:6379> ZSCORE k68 m2
(nil)
zincrby
ZINCRBY key increment member
zset increment by
* increment
: 步长。
* member
: 指定的成员
为有序集合key
的成员member
的score
值加上 increment
。
如果key
或者member
不存在, 则新增一个元素. 相当于 zadd
.
# 插入一个不存在的key。
127.0.0.1:6379> ZINCRBY k69 10 m1
"10"
127.0.0.1:6379> ZRANGE k69 0 -1 WITHSCORES
1) "m1"
2) "10"
#累加
127.0.0.1:6379> ZINCRBY k69 10 m1
"20"
127.0.0.1:6379> ZRANGE k69 0 -1 WITHSCORES
1) "m1"
2) "20"
# 累加一个负数
127.0.0.1:6379> ZINCRBY k69 -30 m1
"-10"
127.0.0.1:6379> ZRANGE k69 0 -1 WITHSCORES
1) "m1"
2) "-10"
zcard
ZCARD key
返回 有序集合的key
中的元素个数.即member
的个数。
不存在的时候,返回0
.
# 返回member的个数
127.0.0.1:6379> ZADD k70 1 m1 2 m2 3 m3
(integer) 3
127.0.0.1:6379> ZCARD k70
(integer) 3
127.0.0.1:6379> ZADD k70 4 m4 5 m5
(integer) 2
127.0.0.1:6379> ZCARD k70
(integer) 5
# 不存在的时候,返回0
127.0.0.1:6379> EXISTS k70_1
(integer) 0
127.0.0.1:6379> ZCARD k70_1
(integer) 0
zcount
ZCOUNT key min max
返回 score
值在min
和 max
之间的元素的个数。包括等于 min
和 max
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
(integer) 10
127.0.0.1:6379> ZCOUNT k71 2 5
(integer) 4
#不存在的key或者不在区间内时,返回0
127.0.0.1:6379> zcount k71_1 0 10
(integer) 0
127.0.0.1:6379> zcount k71 11 12
(integer) 0
127.0.0.1:6379> zcount k71 12 11
(integer) 0
127.0.0.1:6379>
zrange
ZRANGE key start stop [WITHSCORES]
这个命令我们已经用过,就是返回指定开始结束位置上的元素. 从 0
开始。
127.0.0.1:6379> ZRANGE k72 1 3
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> ZRANGE k72 1 3 WITHSCORES
1) "m2"
2) "2"
3) "m3"
4) "3"
5) "m4"
6) "4"
zrevrange
ZREVRANGE key start stop [WITHSCORES]
返回有序集合中指定区间的成员。
其中成员的位置按 score
值递减(从大到小)来排列。 具有相同 score
值的成员按字典序的逆序(reverse lexicographical order
)排列。
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
(integer) 10
# 使用 zrange 正序返回数据
127.0.0.1:6379> ZRANGE k73 0 3 WITHSCORES
1) "m1"
2) "1"
3) "m2"
4) "2"
5) "m3"
6) "3"
7) "m4"
8) "4"
# 使用 zrevrange 倒序返回数据
127.0.0.1:6379> ZREVRANGE k73 0 3 WITHSCORES
1) "m10\x11"
2) "10"
3) "m9"
4) "9"
5) "m8"
6) "8"
7) "m7"
8) "7"
zrangebyscore
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
zset range by score
类似 zrange
, 不过是按照 score
的值进行排序的。
[LIMIT offset count]
, 是从offset开始,返回count
个。
# 返回 score值在 [9,10]之间的member。
127.0.0.1:6379> ZADD k74 1 m1 2 m2 3 m3 4 m4 5 m5 6 m6 7 m7 8 m8 9 m9 10 m10
(integer) 10
127.0.0.1:6379> ZRANGEBYSCORE k74 9 10 WITHSCORES
1) "m9"
2) "9"
3) "m10\x11"
4) "10"
# 从第2个(区间内索引为1)开始,返回1个元素
127.0.0.1:6379> ZRANGEBYSCORE k74 9 10 WITHSCORES LIMIT 1 1
1) "m10\x11"
2) "10"
zrevrangebyscore
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
返回有序集 key
中, score
值介于 max
和 min
之间(默认包括等于 max
或 min
)的所有的成员。有序集成员按 score
值递减(从大到小)的次序排列。
注意各个参数的位置哦。这里和 zrevrange
的参数不一样。
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
(integer) 10
127.0.0.1:6379> ZREVRANGEBYSCORE k75 8 6 WITHSCORES
1) "m8"
2) "8"
3) "m7"
4) "7"
5) "m6"
6) "6"
127.0.0.1:6379> ZREVRANGEBYSCORE k75 8 6 WITHSCORES LIMIT 1 1
1) "m7"
2) "7"
zrank
ZRANK key member
返回有序集 key
中成员 member
的排名。其中有序集成员按 score
值递增(从小到大)顺序排列
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
(integer) 10
127.0.0.1:6379> zrank k76 m4
(integer) 3
127.0.0.1:6379> zrank k76 m10
(integer) 9
zrevrank
ZREVRANK key member
返回有序集 key
中成员 member
的排名。其中有序集成员按 score
值递减(从大到小)排序。
排名以 0
为底,也就是说, score
值最大的成员排名为 0
。
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
(integer) 10
127.0.0.1:6379> ZREVRANK k77 m10
(integer) 0
127.0.0.1:6379> ZREVRANK k77 m4
(integer) 6
zrem
ZREM key member [member ...]
zset remove
移除有序集 key
中的一个或多个成员,不存在的成员将被忽略。
当 key
存在但不是有序集类型时,返回一个错误。
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
(integer) 10
# 删除m2,m3,m4
127.0.0.1:6379> ZREM k78 m2 m3 m4
(integer) 3
127.0.0.1:6379> ZRANGE k78 0 -1 WITHSCORES
1) "m1"
2) "1"
3) "m5"
4) "5"
5) "m6"
6) "6"
7) "m7"
8) "7"
9) "m8"
10) "8"
11) "m9"
12) "9"
13) "m10"
14) "10"
zremrangebyrank
ZREMRANGEBYRANK key start stop
移除有序集 key
中,指定排名(rank
)区间内的所有成员。
区间分别以下标参数 start
和 stop
指出,包含 start
和 stop
在内。
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
(integer) 10
# 删除排名第2到排名第4的member
127.0.0.1:6379> ZREMRANGEBYRANK k79 1 3
(integer) 3
127.0.0.1:6379> ZRANGE k79 0 -1 WITHSCORES
1) "m1"
2) "1"
3) "m5"
4) "5"
5) "m6"
6) "6"
7) "m7"
8) "7"
9) "m8"
10) "8"
11) "m9"
12) "9"
13) "m10"
14) "10"
ZREMRANGEBYSCORE key min max
移除有序集 key
中,所有 score
值介于 min
和 max
之间(包括等于 min
或 max
)的成员。
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
(integer) 10
# 删除 score>=1 and score <=9 的元素
127.0.0.1:6379> ZREMRANGEBYSCORE k80 1 9
(integer) 9
127.0.0.1:6379> zrange k80 0 -1 WITHSCORES
1) "m10"
2) "10"
ZRANGEBYLEX key min max [LIMIT offset count]
当有序集合的所有成员都具有相同的分值时, 有序集合的元素会根据成员的字典序(lexicographical ordering
)来进行排序, 而这个命令则可以返回给定的有序集合键 key
中, 值介于 min
和 max
之间的成员。
注意:
合法的 min
和 max
参数必须包含 ( 或者 [
, 其中 (
表示开区间(指定的值不会被包含在范围之内), 而 [
则表示闭区间(指定的值会被包含在范围之内)。
特殊值 +
和 -
在 min
参数以及 max
参数中具有特殊的意义, 其中 +
表示正无限, 而 -
表示负无限。 因此, 向一个所有成员的分值都相同的有序集合发送命令 ZRANGEBYLEX
, 命令将返回有序集合中的所有元素
lex
:
表示如果score
相等,则按照member
的字典顺序排序。
此外这个命令, 比如ZRANGBYSCORE
稍微强大一点儿. 可以指定区间范围, 当只知道member
,不知道score的时候,可以是使用带有lex
的命令。
127.0.0.1:6379> zadd k81 1 a 2 b 3 c 4 d 5 f 6 g
(integer) 6
# 返回 score值在a的score值和c的score值之间的member
# 即: score> Score(a) && score <= Score(c)
127.0.0.1:6379> ZRANGEBYLEX k81 (a [c
1) "b"
2) "c"
# 返回 小于等于c的Score值的元素
127.0.0.1:6379> ZRANGEBYLEX k81 - [c
1) "a"
2) "b"
3) "c"
# 返回所有元素
127.0.0.1:6379> ZRANGEBYLEX k81 - +
1) "a"
2) "b"
3) "c"
4) "d"
5) "f"
6) "g"
zlexcount
ZLEXCOUNT key min max
对于一个所有成员的分值都相同的有序集合键 key
来说, 这个命令会返回该集合中, 成员介于 min
和 max
范围内的元素数量。
127.0.0.1:6379> zadd k82 1 a 2 b 3 c 4 d 5 f 6 g
(integer) 6
127.0.0.1:6379> ZLEXCOUNT k82 2 5
(error) ERR min or max not valid string range item
127.0.0.1:6379> ZLEXCOUNT k82 a b
# 大于Score(a),小于等于Score(b)的member,只有b.
127.0.0.1:6379> ZLEXCOUNT k82 (a [b
(integer) 1
# 大于Score(a),小于等于Score(d)的member,有b.c.d,三个
127.0.0.1:6379> ZLEXCOUNT k82 (a [d
(integer) 3
zremrangebylex
ZREMRANGEBYLEX key min max
对于一个所有成员的分值都相同的有序集合键 key
来说, 这个命令会移除该集合中, 成员介于 min
和 max
范围内的所有元素。
127.0.0.1:6379> zadd k83 1 a 2 b 3 c 4 d 5 f 6 g
(integer) 6
# 删除 score值在 (Score(a),Score(c)] 之间的member
127.0.0.1:6379> ZREMRANGEBYLEX k83 (a [c
(integer) 2
# 删除了,b,c
127.0.0.1:6379> zrange k83 0 -1
1) "a"
2) "d"
3) "f"
4) "g"
ZSCAN key cursor [MATCH pattern] [COUNT count]
这是一个查询命令。 同 SCAN
命令. 可以参考这篇文章 010-其他命令
SCAN
命令是一个基于游标的迭代器(cursor based iterator
): SCAN
命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN
命令的游标参数, 以此来延续之前的迭代过程。
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
(integer) 10
127.0.0.1:6379> zscan k84 0 MATCH m* COUNT 3
1) "0"
2) 1) "m1"
2) "1"
3) "m2"
4) "2"
5) "m3"
6) "3"
7) "m4"
8) "4"
9) "m5"
10) "5"
11) "m6"
12) "6"
13) "m7"
14) "7"
15) "m8"
16) "8"
17) "m9"
18) "9"
19) "m10"
20) "10"
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
值。演示
127.0.0.1:6379> ZADD k85_1 1 m1 2 m2 3 m3
(integer) 3
127.0.0.1:6379> ZADD k85_2 1 m1 4 m4 5 m5
(integer) 3
127.0.0.1:6379> ZUNIONSTORE k85 2 k85_1 k85_2
(integer) 5
127.0.0.1:6379> zrange k85 0 -1 WITHSCORES
1) "m1"
2) "2"
3) "m2"
4) "2"
5) "m3"
6) "3"
7) "m4"
8) "4"
9) "m5"
10) "5"
# 演示 Weights参数: WEIGHTS 2 3
# 指: 第一个zset的所有元素 *2 ,第二个有序集合中的元素 *3
127.0.0.1:6379> ZUNIONSTORE k85 2 k85_1 k85_2 WEIGHTS 2 3
(integer) 5
127.0.0.1:6379> ZRANGE k85 0 -1 WITHSCORES
1) "m2"
2) "4"
3) "m1"
4) "5"
5) "m3"
6) "6"
7) "m4"
8) "12"
9) "m5"
10) "15"
# 演示 Weights参数: WEIGHTS 2 4
# 指: 第一个zset的所有元素 *2 ,第二个有序集合中的元素 *3
127.0.0.1:6379> ZUNIONSTORE k85 2 k85_1 k85_2 WEIGHTS 2 4
(integer) 5
127.0.0.1:6379> ZRANGE k85 0 -1 WITHSCORES
1) "m2"
2) "4"
3) "m1"
4) "6"
5) "m3"
6) "6"
7) "m4"
8) "16"
9) "m5"
10) "20"
语法
解释
计算给定的一个或多个有序集的交集,其中给定 key
的数量必须以 numkeys
参数指定,并将该交集(结果集)储存到 destination
。
默认情况下,结果集中某个成员的 score
值是所有给定集下该成员 score
值之和.
127.0.0.1:6379> zadd k86_1 1 m1 2 m2 3 m3 4 m4
(integer) 4
127.0.0.1:6379> ZADD k86_2 20 m2 30 m3 50 m5
(integer) 3
127.0.0.1:6379> ZINTERSTORE k86 2 k86_1 k86_2
(integer) 2
# 取交集(默认相加)
127.0.0.1:6379> zrange k86 0 -1 WITHSCORES
1) "m2"
2) "22"
3) "m3"
4) "33"
# WEIGTHS 参数和上面的 ZUNIONSTORE命令一样.
# 这里演示一下, AGGREGATE参数
# 默认使用的是SUM. 就是本命令中上面的例子了.
# 下面演示MIN 和 MAX
127.0.0.1:6379> ZINTERSTORE k86 2 k86_1 k86_2 AGGREGATE MIN
(integer) 2
127.0.0.1:6379> ZRANGE k86 0 -1 WITHSCORES
1) "m2"
2) "2"
3) "m3"
4) "3"
127.0.0.1:6379> ZINTERSTORE k86 2 k86_1 k86_2 AGGREGATE MAX
(integer) 2
127.0.0.1:6379> ZRANGE k86 0 -1 WITHSCORES
1) "m2"
2) "20"
3) "m3"
4) "30"
zset
的内部结构这里我们主要看skiplist
,如果忘记了hashtable
,就看着这篇文章
我们从命令zadd
入手,找到zset
的add
通用方法zaddGenericCommand(c,ZADD_NONE);
来看一下。省略了部分代码。
...
if (server.zset_max_ziplist_entries == 0 ||
server.zset_max_ziplist_value < sdslen(c->argv[scoreidx+1]->ptr))
{
/// 创建 OBJ_ENCODING_SKIPLIST 编码的结构
zobj = createZsetObject();
} else {
/// 创建 OBJ_ZSET 编码的结构
zobj = createZsetZiplistObject();
}
....
来看一下 createZsetObject()
方法的实现,就再清晰不过了。
/// 创建 Zset 对象。
robj *createZsetObject(void) {
zset *zs = zmalloc(sizeof(*zs));
robj *o;
zs->dict = dictCreate(&zsetDictType, NULL);
zs->zsl = zslCreate();
o = createObject(OBJ_ZSET, zs);
o->encoding = OBJ_ENCODING_SKIPLIST;
return o;
}
到这里,就是我们和 zset
这种数据类型的初次深入见面了。 我们先看下 zset
这种结构体的定义。
/// 有序集合的结构定义
typedef struct zset {
/// 字典,键为成员,值为score
/// 用于支持 O(1) 复杂度的按成员分值操作。
dict *dict;
/// 跳跃表,按分值排序成员
/// 用于支持平均复杂度为 O(logN)的按分值定位成员以及范围的操作。
zskiplist *zsl;
} zset;
dict
前面已经看过了,这里来看下zskiplist
。
skiplist
/// 跳跃表
typedef struct zskiplist {
/// 表头,表尾
struct zskiplistNode *header, *tail;
/// 表中节点的数量
unsigned long length;
/// 表中层数最大的节点的层数
int level;
} zskiplist;
typedef struct zskiplistNode {
sds ele;
/// 分数
double score;
/// 后退的指针
struct zskiplistNode *backward;
/// 层
struct zskiplistLevel {
/// 前进指针
struct zskiplistNode *forward;
/// 跨度
unsigned long span;
} level[];
} zskiplistNode;
ZSET
有序集合。底层的数据结构是使用的是 skipList
和 hashtable
. 关于Skiplist
的初步了解文章穿梭机 和 Redis
中 skiplist
的实现源码解读(请期待,已经安排上了)Redis ZSET
数据类型的基础使用场景. 关键字有 有序
,排名
,权重
等.ZSET
的 20
个常有命令。 后面我会针对这 20
个命令的实现进行简单的分享.Redis
中的数据结构的实现,还是那句话,Redis
的数据结构是动态编码的, ZSET
是有hashtable
和 skiplist
实现的。 skiplist 是一个非常高效的数据结构,增删查的效率都是 O(logN)
. 实现原理可以参考这篇文章直通车,里面有几种流行的语言的实现,可以针对自己擅长的语言进行查看。