【Redis05】Redis基础:Sorted Set相关操作

Redis基础学习:Sorted Set相关操作

Redis 五大常用数据类型的最后一个了,同时也是最复杂的,那就是我们今天要学习的 Sorted Set ,也可以叫作 有序集合 。同样是集合,但是它相比 Set 多了一个可以设置分数的功能,利用这个功能,就可以为这个集合元素添加一个排序的依据,这也就是有序集合的由来。

基本操作

还是先从一些基本的操作命令入手来进行 有序集合 的学习。对于 有序集合 来说,因为多了一个分数,所以它的添加修改之类的操作也会多一个数据元素需要我们指定。

添加修改

127.0.0.1:6379> zadd a 1 A 2 B 3 C 1.5 D 2.3 E 3.45 F 1.22 G 2.67 H 3.28 I 1.36 J 2.78 K 3.21 L
(integer) 12

127.0.0.1:6379> zadd ch 2.4 E 2.0 B
(integer) 2

127.0.0.1:6379> Zadd a incr 2 B
"4"

127.0.0.1:6379> ZINCRBY a 1 B
"5"

ZADD 的命令参数是这样的。

ZADD key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...]

score 代表的就是分数,而 member 就是我们实际要保存的数据。上面的例子中,我们还使用了一个 ZINCRBY 命令来实现某一个元素数据分数的增加,但其实直接使用 ZADD 的 INCR 参数也可以实现。

另外,分数都是浮点类型的,可以是整数,也可以是小数,当你添加完成之后,在这个集合中就会按照这个分数进行有序排列。

索引查询

我们先使用索引查询的方式看看刚才添加的数据的结果。使用的是 ZRANGE 命令,它和 LRANGE 是非常类似的,我们可以指定下标范围,也可以使用 0 -1 来返回所有数据。

127.0.0.1:6379> ZRANGE a 0 -1 withscores
 1) "A"
 2) "1"
 3) "G"
 4) "1.22"
 5) "J"
 6) "1.3600000000000001"
 7) "D"
 8) "1.5"
 9) "E"
10) "2.2999999999999998"
11) "H"
12) "2.6699999999999999"
13) "K"
14) "2.7799999999999998"
15) "C"
16) "3"
17) "L"
18) "3.21"
19) "I"
20) "3.2799999999999998"
21) "F"
22) "3.4500000000000002"
23) "B"
24) "5"

127.0.0.1:6379> ZRANGE a 0 1
1) "C"
2) "A"
127.0.0.1:6379> ZRANGE a 0 1 withscores
1) "D"
2) "0.25"
3) "A"
4) "1"

ZRANGE 的 withscores 参数是同时返回数据和分数,如果不加这个参数的话,就只会返回集合中的数据内容。

从返回的数据可以看出,目前我们的数据已经是按照 score 分数由低到高排列了,而且,非常重要的一点,所有的分数都有 精度问题 。关于精度问题是计算机基础知识的内容了,大家只要知道在 Redis 中如果在 有序集合 中使用浮点型作为分数的话,也会有这个问题。

另外我们再看一下 ZADD 中的 GT、LT 参数,它们的意思是如果要增加或者修改一条数据,需要原来的分数符合 GT 大于 或者 LT 小于指定分数的规则才可以更新成功。

127.0.0.1:6379> zadd a lt 0.25 "C"
(integer) 0
127.0.0.1:6379> ZRANGE a 0 1 withscores
1) "C"
2) "0.25"
3) "D"
4) "0.25"

GT、LT 和 NX、XX 选项是相互排斥的,无法同时使用。

目前,C 和 D 元素同样都是 0.25 分,当多个成员有相同的分数时,他们将是 有序的字典(ordered lexicographically),这种字典仍由分数作为第一排序条件,然后,相同分数的成员按照字典规则相对排序。另外,同样身为集合,它们的值,也就是真实的成员数据是不能有重复的。也就是说,有序集合 中,分数可以相同,但值不能相同。

删除

删除就比较简单了,使用一个 ZREM 命令就可以了,可以删指定 key 下面的多个元素。

127.0.0.1:6379> zrem a B C D
(integer) 3

弹出

对于 有序集合 来说,同样也有弹出的命令。也正因为有了这个功能,就可以让我们非常方便地实现一个 有序队列 或者叫 优先级队列 的功能。

127.0.0.1:6379> ZPOPMAX a
1) "F"
2) "3.4500000000000002"
127.0.0.1:6379> ZPOPMAX a 2
1) "I"
2) "3.2799999999999998"
3) "L"
4) "3.21"

127.0.0.1:6379> ZPOPMIN a
1) "C"
2) "0.25"
127.0.0.1:6379> ZPOPMIN a 3
1) "A"
2) "1"
3) "J"
4) "1.3600000000000001"
5) "E"
6) "2"

127.0.0.1:6379> ZRANGE a 0 -1
1) "H"
2) "K"
3) "B"

命令非常简单,ZPOPMIN 弹出分数最小的,ZPOPMAX 弹出最大的,它们也可以指定一次弹出多少数据。

复杂查询与删除

想必大家也猜到了,对于 有序集合 来说,操作肯定不止上面那一点点,毕竟这个可是五大基础数据类型中最复杂的一个。而且又有数据值,又有分数,又有下标,怎么着咱们也得能通过分数啥的来进行查询吧。没问题,这些功能都都有的,我们一个一个来看,不过首先我们来看一个反转的,ZRANGE 是按分数从低到高排列的,要反过来查询只需要使用 ZREVRANGE 命令就可以了。

127.0.0.1:6379> zadd a 1 A 2 B 3 C 1.5 D 2.3 E 3.45 F 1.22 G 2.67 H 3.28 I 1.36 J 2.78 K 3.21 L
(integer) 12
127.0.0.1:6379> ZREVRANGE a 0 -1 withscores
 1) "F"
 2) "3.4500000000000002"
 3) "I"
 4) "3.2799999999999998"
 5) "L"
 6) "3.21"
 7) "C"
 8) "3"
 9) "K"
10) "2.7799999999999998"
11) "H"
12) "2.6699999999999999"
13) "E"
14) "2.2999999999999998"
15) "B"
16) "2"
17) "D"
18) "1.5"
19) "J"
20) "1.3600000000000001"
21) "G"
22) "1.22"
23) "A"
24) "1"

接下来,马上就来看按照分数查询,使用的是 ZRANGEBYSCORE 命令,直接把下标换成分数范围即可,比如下面我们查询的是 2 到 4 分之间的数据。

127.0.0.1:6379> ZRANGEBYSCORE a 2 4 withscores
 1) "B"
 2) "2"
 3) "E"
 4) "2.2999999999999998"
 5) "H"
 6) "2.6699999999999999"
 7) "K"
 8) "2.7799999999999998"
 9) "C"
10) "3"
11) "L"
12) "3.21"
13) "I"
14) "3.2799999999999998"
15) "F"
16) "3.4500000000000002"

127.0.0.1:6379> ZRANGEBYSCORE a 2 4 withscores limit 2 2
1) "H"
2) "2.6699999999999999"
3) "K"
4) "2.7799999999999998"

注意,ZRANGEBYSCORE 还有 LIMIT 参数哦,和 MySQL 中的 LIMIT OFFSET 一样,而且更加方便好用。

127.0.0.1:6379> ZRANGEBYSCORE a (2 3 withscores
1) "E"
2) "2.2999999999999998"
3) "H"
4) "2.6699999999999999"
5) "K"
6) "2.7799999999999998"
7) "C"
8) "3"

另外,我们还可以指定开闭区间,比如上面这段在 2 前面加了一个 ( ,表示的就是不包含 2 本身的区间内容。同样,它也有一个反向倒序的排列命令 ZREVRANGEBYSCORE 。

127.0.0.1:6379> ZREVRANGEBYSCORE a 4 2
1) "F"
2) "I"
3) "L"
4) "C"
5) "K"
6) "H"
7) "E"
8) "B"

同分区间

前面我们说过 有序字典 问题,也就是同分数据会再按照它的具体数据值来进行排序。那么对于这类同分数据来说,要进行范围查询也可以根据数值内容,使用的命令是 ZRANGEBYLEX 。它的参数很好玩,可以设置成 + - 号。

127.0.0.1:6379> zadd c 1 a 1 aa 1 abc 1 apple 1 b 1 c 1 d 1 d1 1 dd 1 z 1 z1
(integer) 11
127.0.0.1:6379> ZRANGEBYLEX c - +
 1) "a"
 2) "aa"
 3) "abc"
 4) "apple"
 5) "b"
 6) "c"
 7) "d"
 8) "d1"
 9) "dd"
10) "z"
11) "z1"
127.0.0.1:6379>
127.0.0.1:6379> ZRANGEBYLEX c + -
(empty array)

其实这两个符号的意思就是 最大+ 和 最小- ,上面的意思就是从最小的显示到最大的,其实就是全部显示的。但是对于 ZRANGEBYLEX 来说,它是严格遵守从小到大的正序的,所以如果是 - + 这种形式,就无法返回数据,如果要倒序从大到小的话,我们需要使用 ZREVRANGEBYLEX 命令。接下来我们再看看指定数值如何使用。

127.0.0.1:6379> ZRANGEBYLEX c [a (d
1) "a"
2) "aa"
3) "abc"
4) "apple"
5) "b"
6) "c"

有意思吗?[ 表示小于等于,( 表示小于,同样也是开闭区间的表示。正常情况下使用 ZRANGEBYLEX 都需要添加这两个符号。上面的例子返回的就是数值在 a 和 d 之间的数据,包括 a 但不包括 d 。

我们再来看下倒序的 ZREVRANGEBYLEX 的使用。

127.0.0.1:6379> ZREVRANGEBYLEX c + -
 1) "z1"
 2) "z"
 3) "dd"
 4) "d1"
 5) "d"
 6) "c"
 7) "b"
 8) "apple"
 9) "abc"
10) "aa"
11) "a"
127.0.0.1:6379> ZREVRANGEBYLEX c [d (a
1) "d"
2) "c"
3) "b"
4) "apple"
5) "abc"
6) "aa"

除了范围查询之外,我们还可以根据同分数值进行删除操作,使用的是 ZREMRANGEBYLEX 命令。

127.0.0.1:6379> ZRANGEBYLEX c [ab (b
1) "abc"
2) "apple"
127.0.0.1:6379> ZREMRANGEBYLEX c [ab (b
(integer) 2
127.0.0.1:6379> ZRANGEBYLEX c - +
 1) "a"
 2) "aa"
 3) "b"
 4) "c"
 5) "d"
 6) "d1"
 7) "dd"
 8) "dobble"
 9) "z"
10) "z1"

此外,还有数量查询的操作 ZLEXCOUNT 。

127.0.0.1:6379> ZLEXCOUNT c - +
(integer) 9

127.0.0.1:6379> ZLEXCOUNT c [a [d
(integer) 5

对于上面的几种查询来说,其实 ZRANGE 都可以通过添加参数的方式支持。

ZRANGE key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES]

比如 BYLEX 参数就可以实现上面 ZRANGEBYLEX 的作用,REV 就是倒序的意思。

另外,同分操作的这几个命令功能还有一些限制,比如:

  • 如果有序集合中的成员分数有不一致的,返回的结果就不准。

  • 默认是以ASCII字符集的顺序进行排列。如果成员字符串包含utf-8这类字符集的内容,就会影响返回结果,所以建议不要使用。

因此,这些命令确实不常用,咱们就了解一下就好了。

范围删除

ZREM 可以实现集合内元素的删除,但要指定数据值删除,我们也可以通过另外两个命令进行批量的范围删除。第一个就是 ZREMRANGEBYRANK ,从字面意思理解它是根据排名删除,其实我们也可以理解为是根据正序下标进行删除。

127.0.0.1:6379> zrange a 0 -1
 1) "A"
 2) "G"
 3) "J"
 4) "D"
 5) "B"
 6) "E"
 7) "H"
 8) "K"
 9) "C"
10) "L"
11) "I"
12) "F"
127.0.0.1:6379> ZREMRANGEBYRANK a 3 5
(integer) 3
127.0.0.1:6379> zrange a 0 -1
1) "A"
2) "G"
3) "J"
4) "H"
5) "K"
6) "C"
7) "L"
8) "I"
9) "F"

除了排名删除之外,还可以根据分数范围进行删除,使用 ZREMRANGEBYSCORE 命令。

127.0.0.1:6379> zrange a 0 -1 withscores
 1) "A"
 2) "1"
 3) "G"
 4) "1.22"
 5) "J"
 6) "1.3600000000000001"
 7) "H"
 8) "2.6699999999999999"
 9) "K"
10) "2.7799999999999998"
11) "C"
12) "3"
13) "L"
14) "3.21"
15) "I"
16) "3.2799999999999998"
17) "F"
18) "3.4500000000000002"
127.0.0.1:6379> ZREMRANGEBYSCORE a 1 2
(integer) 3
127.0.0.1:6379> zrange a 0 -1 withscores
 1) "H"
 2) "2.6699999999999999"
 3) "K"
 4) "2.7799999999999998"
 5) "C"
 6) "3"
 7) "L"
 8) "3.21"
 9) "I"
10) "3.2799999999999998"
11) "F"
12) "3.4500000000000002"

其它

上面的内容看着很乱很晕吧?别急,还有呢,都说了它是最复杂也是命令最多的一个数据类型嘛。

数量

我们可以获取集合中元素的数量,使用 ZCARD 命令,和 SCARD 是一样的。

127.0.0.1:6379> zcard a
(integer) 6

当然,我们也可以获取指定分数范围内容的元素数量,使用的是 ZCOUNT 命令。

127.0.0.1:6379> zcount a -inf +inf
(integer) 6
127.0.0.1:6379> zcount a 2 3
(integer) 3

获取信息

通过数据值,我们可以获取该数据的一些信息,首先来看一下获取它的分数信息,使用 ZSCORE 命令。

127.0.0.1:6379> ZSCORE a K
"2.7799999999999998"
127.0.0.1:6379> ZSCORE a H
"2.6699999999999999"

除了单个获取外,也可以指获取多个数据值的分数信息,如果指定的数据值不存在的话,会返回一个 nil 。

127.0.0.1:6379> ZMSCORE a H K L A
1) "2.6699999999999999"
2) "2.7799999999999998"
3) "3.21"
4) (nil)

我们也可以获取到指定数据值在当前集合中的位置排名信息,使用的是 ZRANK 命令,同样它也是数据在正序从小到大的排列时所在的下标位置。

127.0.0.1:6379> ZRANK a H
(integer) 0
127.0.0.1:6379> ZRANK a K
(integer) 1
127.0.0.1:6379> ZRANK a L
(integer) 3
127.0.0.1:6379> ZRANK a A
(nil)

最后,还可以随机的获取一条数据,这个和 SRANDMEMBER、HRANDFIELD 这些是一样的,它的数量也是可以设置为负值填充的。

127.0.0.1:6379> ZRANDMEMBER a
"C"
127.0.0.1:6379> ZRANDMEMBER a
"K"
127.0.0.1:6379> ZRANDMEMBER a
"F"
127.0.0.1:6379> ZRANDMEMBER a 0
(empty array)
127.0.0.1:6379> ZRANDMEMBER a 2
1) "C"
2) "I"
127.0.0.1:6379> ZRANDMEMBER a -2
1) "C"
2) "H"
127.0.0.1:6379> ZRANDMEMBER a -10
 1) "H"
 2) "L"
 3) "F"
 4) "F"
 5) "L"
 6) "C"
 7) "L"
 8) "I"
 9) "F"
10) "H"
127.0.0.1:6379> ZRANDMEMBER a 10
1) "F"
2) "I"
3) "L"
4) "C"
5) "K"
6) "H

集合操作

集合操作与普通的 Set 区别不大,也都是 差、并、交 的操作,但是,对于 有序集合 来说,因为多了分数的内容,所以对分数的 并、交 会有一些特殊的操作,这个我们后面再看,先创建四个集合并添加一些数据。

127.0.0.1:6379> del a b c
(integer) 3
127.0.0.1:6379> zadd a 1 one 2 two 3 three 4 four
(integer) 4
127.0.0.1:6379> zadd b 1 one 4 four 5 five 7 seven
(integer) 4
127.0.0.1:6379> zadd c 6 six 4 four 2 two
(integer) 3
127.0.0.1:6379> zadd d 1.1 one 22 two
(integer) 2

差集操作 ZDIFF 和 ZDIFFSTORE 命令都是 Redis6.2 提供的。

127.0.0.1:6379> zdiff 2 a b
1) "two"
2) "three"
127.0.0.1:6379> zdiff 2 b c
1) "one"
2) "five"
3) "seven"
127.0.0.1:6379> zdiff 3 a b c
1) "three"
127.0.0.1:6379> zdiff 3 a b c withscores
1) "three"
2) "3"

127.0.0.1:6379> zdiffstore zdiff 3 a b c
(integer) 1
127.0.0.1:6379> zrange zdiff 0 -1
1) "three"

交集操作中,ZINTER 是 6.2 提供的,ZINTERSTORE 早就有了。

127.0.0.1:6379> ZINTER 2 a b
1) "one"
2) "four"
127.0.0.1:6379> ZINTER 2 a b withscores
1) "one"
2) "2"
3) "four"
4) "8"
127.0.0.1:6379> ZINTERSTORE zinter 2 a b
(integer) 2
127.0.0.1:6379> zrange zinter 0 -1
1) "one"
2) "four"

并集操作中,ZUNION 也是 6.2 提供的。

127.0.0.1:6379> ZUNION 2 a b
1) "one"
2) "two"
3) "three"
4) "five"
5) "seven"
6) "four"
127.0.0.1:6379> ZUNIONSTORE zunion 2 a b
(integer) 6
127.0.0.1:6379> zrange zunion 0 -1
1) "one"
2) "two"
3) "three"
4) "five"
5) "seven"
6) "four"

上面的 差、交、并 操作命令后面都会跟一个数字,它代表的意思是有几个集合一些进行操作,这个数字可不能写错哦。

最重要的,我们要来看看交和并时的分数情况。因为差本身就是返回在指定集合中不存在的数据,所以差值的分数和原始的分类不会有什么变化。但并和交就不一样了。

127.0.0.1:6379> zinter 2 a d  withscores
1) "one"
2) "2.1000000000000001"
3) "two"
4) "24"

上面的分数是不是有点怪?在集合 a 中,one 是 1 ,集合 d 中,one 是 1.1 ,结果相交之后变成了 2.1000000000000001 。似乎是进行了一次相加操作。没错,你猜对了,对于分数的处理,如果是 交 或者 并 操作,默认情况下就是把它们相加。不过我们也可以通过参数指定进行别的操作,比如指定选用最大的或者最小的。

127.0.0.1:6379> zinter 2 a d aggregate max  withscores
1) "one"
2) "1.1000000000000001"
3) "two"
4) "22"
127.0.0.1:6379> zinter 2 a d aggregate min withscores
1) "one"
2) "1"
3) "two"
4) "2"
127.0.0.1:6379> zinter 2 a d aggregate sum withscores
1) "one"
2) "2.1000000000000001"
3) "two"
4) "24"

另外,我们还可以指定一个乘法因子,让指定集合的元素进行乘法操作之后再相加或者最大最小返回,使用的是 WEIGHTS 参数。

127.0.0.1:6379> zinter 2 a d weights 1 2 withscores
1) "one"
2) "3.2000000000000002"
3) "two"
4) "46"
127.0.0.1:6379> zinter 2 a d weights 2 1 withscores
1) "one"
2) "3.1000000000000001"
3) "two"
4) "26"
127.0.0.1:6379> zinter 2 a d weights 2 2 withscores
1) "one"
2) "4.2000000000000002"
3) "two"
4) "48"

上面这三个例子看得明白嘛?刚开始学的时候这一块把我彻底搞蒙了。WEIGHTS 后面跟着的数字表示的其实是给第几个集合中的元素乘以几。比如说第一个 weights 1 2 ,表示的是给 a 集合中的元素乘 1 ,给 d 集合中的元素乘 2 ,然后返回默认它们相加的结果。

阻塞操作

阻塞操作熟悉吧?之前我们讲 List 的时候就讲过呀,BLPOP 还记得吧,具体是干嘛的我也就不多解释了,直接看代码吧。

127.0.0.1:6379> BZPOPMAX c 0
1) "c"
2) "six"
3) "6"
127.0.0.1:6379> BZPOPMAX c 0
1) "c"
2) "four"
3) "4"
127.0.0.1:6379> BZPOPMAX c 0
1) "c"
2) "two"
3) "2"
127.0.0.1:6379> BZPOPMAX c 0
// 阻塞

再开一个客户端添加数据,上面的阻塞就解除了并且弹出了数据。

//客户端2
127.0.0.1:6379> zadd c 2 two
(integer) 1

// 客户端1
1) "c"
2) "two"
3) "2"
(38.19s)

使用的命令就是 BZPOPMAX 和 BZPOPMIN 。

127.0.0.1:6379> BZPOPMIN a b 0
1) "a"
2) "one"
3) "1"

增量迭代

增量迭代这一块也不用过多解释了,之前的 Hash 和 Set 中都有,不记得的小伙伴也可以回去再看下哦。同样我们还是需要准备数据。

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

for ($i = 0; $i <= 100000; $i++) {
    $redis->zAdd("phpzadd", [], rand(1, 100), "k" . $i);
}

然后使用 ZSCAN 命令就可以开始进行 有序集合 的增量迭代了。

127.0.0.1:6379> ZSCAN phpzadd 0
1) "8192"
2)  1) "k99957"
    2) "94"
    3) "k21240"
    4) "57"
    5) "k50440"
    6) "92"
    7) "k81565"
    8) "68"
    9) "k78377"
   10) "100"
   11) "k67734"
   12) "50"
   13) "k48699"
   14) "55"
   15) "k40838"
   16) "12"
   17) "k83378"
   18) "18"
   19) "k65820"
   20) "37"
127.0.0.1:6379> ZSCAN phpzadd 8192
1) "53248"
2)  1) "k95672"
    2) "73"
    3) "k86990"
    4) "86"
    5) "k25480"
    6) "59"
    7) "k35132"
    8) "73"
    9) "k6530"
   10) "10"
   11) "k97339"
   12) "69"
   13) "k28806"
   14) "29"
   15) "k94002"
   16) "3"
   17) "k17700"
   18) "16"
   19) "k55468"
   20) "77"

总结

今天的文章很长吧?但其实内容并不多,无非就是比 Set 多了几条命令而已。有序集合 这个数据类型非常强大,积分榜、有序队列、优先级队列都可以用它实现。而且,TP 和 Laravel 中的延时队列功能也是用它实现的哦,怎么延时?想想把 score 用时间戳表示不就行了嘛,具体内容大家可以自己看一下 TP 和 Laravel 中队列这一块的源码哦。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值