乐读窝

左手MongoDB,右手Redis

乐读窝 > 科普学习 > 左手MongoDB,右手Redis

9.3有序集合

书籍名:《左手MongoDB,右手Redis》    作者:谢乾坤



有序集合(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  有序集合名  积分下限  积分上限