9.3有序集合
有序集合(Sorted Set)是Redis的一个数据结构。
有序集合里面的数据跟集合一样,也是不能重复的,但是每一个元素又关联了一个分数(Score),根据这个分数可以对元素进行排序。分数可以重复。
9.3.1 实例39:实现排行榜功能
各种排行榜是我们司空见惯的功能。各位读者是否思考过,各种排行榜是如何实现的呢?
实例描述
分别使用MongoDB和Redis的有序集合来实现排行榜功能。对比传统数据库的排序功能,寻找有序集合实现排序功能的优点。
1.使用传统数据库实现排行榜
这里以 MongoDB 为例来进行说明。这种方法的逻辑非常直接,需要被排名的信息都保存在数据库里面,当需要显示排行榜时,直接读取数据库,然后对结果进行排名。
(1)运行rank_data_to_mongo.py生成测试数据,如图9-15所示。测试数据的user_id对应于用户的id,score对应于用户的积分。
图9-15 测试数据
(2)根据积分对用户进行排序。代码如下:
代码9-15 使用MongoDB对用户积分排序
(3)运行效果如图9-16所示。
图9-16 直接使用MongoDB进行排序
2.使用数据库排序的弊端
具体到一个实际例子,比如说直播网站观众向主播送礼物的排行版,如果直接在数据库里面进行排序,弊端有以下几点:
● 排行榜会实时更新,数据每一次变化都要排序,会对数据库的性能造成影响。
● 频繁更新数据,导致数据库性能下降。
● 数据量太大时排序时间缓慢。
● 对被排序字段添加索引会占用更多空间。
3.使用有序集合进行排序
Redis的有序集合天生就自带排序的功能。
(1)直接把MongoDB中的数据导入到Redis中名为“rank”的有序集合中:
代码9-16 使用有序集合排序
(2)显示某一个特定用户的排名,具体代码如下:
代码9-17 显示特定排名的用户
(3)显示全部用户的排名,具体代码如下:
rank = client.zrevrank('rank', 0, 10000, withscores=True)
(4)运行效果如图9-17所示。
图9-17 排名查询
有序集合还能直接修改某一个值的分数,从而直接改变排序。
9.3.2 实例40:使用Python读写有序集合
有序集合的操作命令有二十多个,对应到Python中也有二十多个方法。本书选择其中常用的几个。
实例描述
在Python中控制Redis,读写有序集合,实现以下功能:
(1)把数据添加到有序集合中。
(2)修改有序集合值的评分。
(3)将有序集合基于“评分”进行排序。
(4)将有序集合基于“位置”进行排序。
(5)根据值查询排名和评分情况。
1.向有序集合添加数据
向有序集合添加数据,使用的方法为“zadd”。它的格式有两种:
方法一:client.zadd(’有序集合名’, 值1, 评分1, 值2, 评分2, 值n, 评分n)
方法二:client.zadd(’有序集合’, 值1=评分1, 值2=评分2, 值3=评分3)
这两种方式的效果是一样的,但是第1种的值可以使用变量,而第2种的值不能使用变量。
例如,代码9-18是一个和年龄相关的有序集合。
代码9-18 使用Python向Redis有序集合中添加数据
其中,主要代码说明如下。
● 第4行代码:其中的name1和name2是变量,它们里面的值分别为“王小二”和“张三”。最后存在Redis中的值也是“王小二”和“张三”。
● 第5行代码:使用Value=score的写法,用这种写法时,Value不能使用变量,只能直接写值。
第4行和第5行效果完全一样。
2.修改评分
修改评分使用的方法名为“zincrby”,格式如下:
client.zincrby(’有序集合名’, 值,改变量)
例如,在age_rank中,把“王小二”的年龄增加三岁,把“小明”的年龄减0.5岁:
代码9-19 修改有序集合的元素评分
3.对有序集合元素基于评分范围进行排序
根据评分范围进行排序,使用的方法分别为“zrangebyscore”和“zrevrangebyscore”。
这两个方法的用法完全相同,差别在于:
● zrangebyscore根据评分按照从小到大的顺序排序。
● zrevrangebyscore根据评分按照从大到小的顺序排序。
它们的使用格式如下:
client.zrangebyscore(’有序集合名’, 评分上限, 评分下限, 结果切片起始位置, 结果数量, withscores=False)
client.zrevrangebyscore(’有序集合名’, 评分上限, 评分下限, 结果切片起始位置, 结果数量, withscores=False)
其中,评分上限、评分下限用于确定排序的范围。例如,评分分布在0~10000,现在只对评分在10~100范围内的值进行排序。排序完成以后,通过设定结果切片的起始位置、结果数量来限定返回的列表的长度。其中,结果切片起始位置、结果数量这两个参数可以同时省略,省略表示返回排序后的所有数据。
提示:
如果withscores设置为False,则返回的结果直接是排序好的值。
如果withscores设置为True,则返回的列表里面的元素是元组。元组的第1个元素是值,第2个元素是评分。
举例,在有序集合rank中,对积分在10~100范围内的人员进行倒序排序,并返回前3条数据,代码如下:
client.zrevrangebyscore('rank', 100, 10, 0, 3)
运行效果如图9-18所示。
图9-18 对积分10~100范围内数据倒序并取前3条
4.对有序集合基于位置进行排序
基于位置范围进行排序,用到的方法名为“zrange”和“zrevrange”。
● zrange对评分按照从小到大的顺序排序。
● zrevrange对评分按照从大到小的顺序排序。
它们的用法如下:
client.zrange(’有序集合名’, 开始位置(含), 结束位置(含), desc=False, withscores=False)
client.zrevrange(’有序集合名’, 开始位置(含), 结束位置(含), withscores=False)
这两个方法,根据0开始的索引找到需要排序的元素范围,然后对这个范围内的数据进行排序。
(1)zrange方法。
如果使用的是zrange方法,则位置“0”是评分最小的元素,位置“1”是评分次小的元素,以此类推。
假设开始位置写为“0”,结束位置写为“4”,则取出最小的5个元素,如图9-19所示。
图9-19 使用zrange取最小5个元素
提示:
与Python列表一样,开始位置和结束位置也可以写为负数,表示从后往前数。
例如,开始位置写为“−4”,结束位置写为“−1”,表示取评分最大的4个元素,且score低的在前,如图9-20所示。
图9-20 使用zrange取最大4个元素
(2)zrevrange方法。
使用zrevrange方法,位置“0”为最大的元素,位置“1”为次大的元素。
如果开始位置写“0”,结束位置写“4”,则取最大的5个元素,如图9-21所示。
图9-21 使用zrevrange取最大5个元素
如果开始位置取“-4”,结束位置取“-1”,则取最小的4个元素,且score高的在前,如图9-22所示。
图9-22 使用zrevrange取最小4个元素
提示:
如果使用zrange方法,同时desc=True,那在底层会自动调用zrevrange方法。因此,如果使用zrange,开始位置为“0”,结束位置为“4”,参数desc=True,则它的作用是取最大的5个元素, 如图9-23所示。千万不要认为是取最小的5个元素再倒序排序。
图9-23 使用zrange并且desc为True
如果withscores为False,则返回的结果直接是排序好的值;如果withscores为True,返回的列表里面的元素是元组,每个元组的第1个元素是值,第2个元素是评分。
5.根据值查询排名,根据值查询评分
(1)使用zrank和zrevrank方法,可以查询一个值在有序列表中的排名。格式如下:
client.zrank(’有序列表名’, ’值’)
client.zrevrank(’有序列表名’, ’值’)
① 使用zrank方法时。
● 如果值存在,则返回值的排名。排名是从0开始的,评分越小则排名越靠近0,评分最小的值的排名为0。
● 如果值不存在,则返回None。
② 使用zrevrank方法时。
● 如果值存在,则返回值的排名。排名是从0开始的,评分越大排名越靠近0,评分最大的值的排名为0。
● 如果值不存在,则返回None。
(2)使用zscore可以查询一个值的评分。格式如下:
client.zscore(’有序列表名’, ’值’)
如果值不存在,则返回None。
6.其他常用方法
(1)查询有序集合里面一共有多少个值,使用的方法名为“zcard”。
格式如下:
client.zcard(’有序集合名’)
如果有序集合不存在,则返回0。
(2)查询在某个评分范围内的值有多少,使用的方法名为zcount。
格式如下:
client.zcount(’有序集合名’, 评分下限, 评分上限)
9.3.3 实例41:在Redis交互环境redis-cli中使用有序集合
实例描述
在redis-cli中读写有序集合,实现以下功能:
(1)添加数据。
(2)修改值的评分。
(3)基于评分和位置进行排序。
(4)查询值的排名和评分。
有序集合在redis-cli中,有一些命令的参数与Python中存在差别,需要特别注意。
1.添加数据
添加数据对应的命令为“zadd”,命令格式如下:
zadd 有序集合名 评分1 值1 评分2 值2 评分n 值n
注意,在redis-cli中,添加数据时“评分在前,值在后”;在Python中,添加数据时“值在前,评分在后”。
2.修改评分
修改评分使用的命令为“zincrby”,命令格式如下:
zincrby 有序集合名 修改的分数 值
如果值不存在,则自动创建,并把修改的分数作为初始评分。
举例:需要在有序集合age_rank中,把王小二的年龄增加10岁,则命令应该写为:
zincrby age_rank 10 王小二
运行效果如图9-24所示。
图9-24 在redis-cli中修改评分
3.基于评分范围排序,基于位置范围排序
基于评分范围排序,使用的命令为“zrangebyscore”和“zrevrangebyscore”。
基于位置范围排序,使用的命令为“zrange”和“zrevrange”。
命令格式为:
zrangebyscore 有序列表名 评分下限 评分上限 WITHSCORES LIMIT 切片开始位置 结果数量
zrevrangebyscore 有序列表名 评分下限 评分上限 WITHSCORES LIMIT 切片开始位置 结果数量
其中,WITHSCORES和LIMIT都是关键字。
● WITHSCORES可以省略。省略以后,只有值没有评分。
● 如果不需要对结果进行切片,则“LIMIT 切片开始位置 结果数量”也可以省略。
zrange 有序集合名 开始位置 结束位置
zrevrange 有序集合名 开始位置 结束位置 WITHSCORES
4.查询值的排名,查询值的评分
● 查询排名使用的命令为“zrank”和“zrevrank”,命令格式如下:
zrank 有序集合名 值
zrevrank 有序集合名 值
● 查询值的评分命令为“zscore”,命令格式如下:
zscore 有序集合名 值
5.其他常用命令
● 查询有序集合中元素的个数,使用的命令为“zcard”,命令格式如下:
zcard 有序集合名
● 查询评分范围内的元素个数,用到的命令为“zcount”,命令格式如下:
zcount 有序集合名 积分下限 积分上限