前面提到的几种数据类型都各有特点,但是如果想对数据进行排序却做不到,想要数据能够按照某种特色进行排序,需要用到一种新类型,sorted_set。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。
在前面set的基础上,对每个元素增加一个score关键字,然后按照score的内容去进行排序。score关键字的内容可以选择显示或者不显示。
常用操作更多操作可以通过help @sorted_set
来查看
添加数据:
zadd key score1 member1 [score2 member2 …]127.0.0.1:6380> zadd age 18 xiaofu 35 james 100 kobe
(integer) 3
127.0.0.1:6380>
需要注意的是对sorted_set中的元素重新利用zadd来赋值一个新的score的时候,会返回0,但是却赋值成功了。可能是因为类似set类型,不允许有重复的元素。
127.0.0.1:6380> zadd age 40 xiaofu
(integer) 0
查看数据:
zrange key start stop [withscores] zrevrange key start stop [withscores]一个是按照从小到大的顺序,另一个是反向。默认是不显示score内容的,如果带上withscores
关键字就会显示score。因为有顺序,就会有索引的概念,就跟前面的list数据类型一样,start
和stop
分别表示开始和结束的索引,从0开始,-1表示最后一位。
127.0.0.1:6380> zrange age 0 -1
1) "james"
2) "xiaofu"
3) "kobe"
127.0.0.1:6380> zrange age 0 -1 withscores
1) "james"
2) "35"
3) "xiaofu"
4) "40"
5) "kobe"
6) "100"
获取最大值
127.0.0.1:6380> zrevrange age 0 0 withscores
1) "kobe"
2) "100"
127.0.0.1:6380>
删除数据
zrem key member [member …]127.0.0.1:6380> zrem age kobe
(integer) 1
127.0.0.1:6380> zrevrange age 0 -1 withscores
1) "xiaofu"
2) "43"
3) "james"
4) "35"
127.0.0.1:6380>
既然主要是根据score的值来进行的排序,当然也可以做更多的操作
按照score的值进行条件获取
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] zrevrangebyscore key min max [WITHSCORES] [LIMIT offset count]这里的limit和mysql里面的功能一模一样
127.0.0.1:6380> zrange age 0 -1 withscores
1) "gigi"
2) "18"
3) "brown"
4) "20"
5) "james"
6) "35"
7) "xiaofu"
8) "43"
9) "kobe"
10) "99"
127.0.0.1:6380> zrangebyscore age 20 50 withscores
1) "brown"
2) "20"
3) "james"
4) "35"
5) "xiaofu"
6) "43"
127.0.0.1:6380> zrangebyscore age 20 50 withscores limit 0 2
1) "brown"
2) "20"
3) "james"
4) "35"
127.0.0.1:6380>
按照score的值去条件删除字段:
zremrangebyscore key min max zremrangebyrank key start stop127.0.0.1:6380> zrange age 0 -1 withscores
1) "gigi"
2) "18"
3) "brown"
4) "20"
5) "james"
6) "35"
7) "xiaofu"
8) "43"
9) "kobe"
10) "99"
127.0.0.1:6380> zremrangebyscore age 20 40
(integer) 2
127.0.0.1:6380> zrange age 0 -1 withscores
1) "gigi"
2) "18"
3) "xiaofu"
4) "43"
5) "kobe"
6) "99"
127.0.0.1:6380>
127.0.0.1:6380> zrange age 0 -1 withscores
1) "gigi"
2) "18"
3) "xiaofu"
4) "43"
5) "kobe"
6) "99"
127.0.0.1:6380> zremrangebyrank age 0 1
(integer) 2
127.0.0.1:6380> zrange age 0 -1 withscores
1) "kobe"
2) "99"
127.0.0.1:6380>
统计元素个数:
zcard key zcount key min max127.0.0.1:6380> zrange age 0 -1 withscores
1) "gigi"
2) "18"
3) "davis"
4) "20"
5) "james"
6) "35"
7) "xiaofu"
8) "40"
9) "kobe"
10) "99"
127.0.0.1:6380> zcard age
(integer) 5
127.0.0.1:6380> zcount age 15 35
(integer) 3
127.0.0.1:6380>
类似于set的集合操作:
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]127.0.0.1:6380> zadd s1 40 aa 50 bb 60 cc
(integer) 3
127.0.0.1:6380> zadd s2 45 aa 55 bb 70 dd
(integer) 3
127.0.0.1:6380> zadd s3 50 aa 60 bb 80 dd
(integer) 3
127.0.0.1:6380> zinterstore ss 3 s1 s2 s3
(integer) 2
127.0.0.1:6380> zrange ss 0 -1 withscores
1) "aa"
2) "135"
3) "bb"
4) "165"
127.0.0.1:6380>
127.0.0.1:6380> zinterstore ss 3 s1 s2 s3 aggregate max
(integer) 2
127.0.0.1:6380> zrange ss 0 -1 withscores
1) "aa"
2) "50"
3) "bb"
4) "60"
127.0.0.1:6380>
127.0.0.1:6380> zunionstore sss 3 s1 s2 s3
(integer) 4
127.0.0.1:6380> zrange sss 0 -1
1) "cc"
2) "aa"
3) "dd"
4) "bb"
127.0.0.1:6380> zrange sss 0 -1 withscores
1) "cc"
2) "60"
3) "aa"
4) "135"
5) "dd"
6) "150"
7) "bb"
8) "165"
127.0.0.1:6380>
某些场景下,例如电影热度榜单,明星热度榜单等,不仅会显示排序的结果还会显示索引。
获取数据对应的索引:
zrank key member zrevrank key member127.0.0.1:6380> zadd scores 100 a 87 b 60 c 76 d
(integer) 4
127.0.0.1:6380> zrank scores b
(integer) 2
127.0.0.1:6380> zrevrank scores b
(integer) 1
127.0.0.1:6380>
需要注意这里显示的是索引,比真正的排名差了1
获取和增加score:
zscore key member zincrby key increment member127.0.0.1:6380> zscore scores c
"60"
127.0.0.1:6380> zincrby scores 1 c
"61"
127.0.0.1:6380> zscore scores c
"61"
127.0.0.1:6380>
注意事项
如果是浮点型的score,存储结构是双精度。需要小心浮点型数据精度丢失的问题,可以参考我的另一篇博客《浮点型数据精度丢失实例详解》
如前面提到的,因为是基于set结构的,所以不能有重复的元素。如果对已存在的元素重新添加不同的score,会返回执行失败,但是score会被新的值覆盖
实际案例
时效性任务管理
例如视频网站会员过期,或者微信群投票过期,微信中历史图片过期等等。这种场景利用前面提到的ttl也是可以做得到,这里我们尝试用sorted_set来完成这个功能。
思路:将用户的id存储到sorted_set中,到期时间线的unix时间戳存储为score,这样最先到期的就排在最上面。每次记录最上面的时间戳,当和现在的时间戳相同时就删除该用户的记录。没有记录的用户就提示过期。
实际的操作也比较简单,这里主要是想引出redis中显示时间戳的函数time
127.0.0.1:6380> time
1) "1581783416"
2) "950338"
127.0.0.1:6380>
其中第一个数字是秒级别的时间戳,基本上就够用了
带权重的消息或任务队列前面学习list数据类型的时候,可以很方便的制作一个消息或者任务队列。但是那个只能按照时间顺序去处理,如果要加权重的会只能用到这里讲的sorted_set。
思路:将任务的权重放在score当中进行排序,利用zpopmax
或者zpopmin
去获取并移除最高权重的任务即可
127.0.0.1:6380> zadd tasks 1 aa 3 bb 4 cc 1 dd 2 ee 4 ff
(integer) 6
127.0.0.1:6380> zrange tasks 0 -1 withscores
1) "aa"
2) "1"
3) "dd"
4) "1"
5) "ee"
6) "2"
7) "bb"
8) "3"
9) "cc"
10) "4"
11) "ff"
12) "4"
127.0.0.1:6380> zpopmin tasks
1) "aa"
2) "1"
127.0.0.1:6380> zrange tasks 0 -1 withscores
1) "dd"
2) "1"
3) "ee"
4) "2"
5) "bb"
6) "3"
7) "cc"
8) "4"
9) "ff"
10) "4"
127.0.0.1:6380>
假设所有多维度的权重要处理,例如部门A的权重高过部门B,但是部门主管的权重高过部门成员。这时候就可以将部门主管编号为100,普通员工编号为101,部门A编号为200,部门B编号为201,这样首先放行政级别再放部门即可,例如100200
或者101200
,先判断高位再判断低位,所以没有问题。
但是这种多维度的问题在设定数字的时候比较考验技巧,位数要相同,不然可能会发生错位比较的情况,同时不同维度的设计也是个技术活。