当前位置:首页 > 篮球快讯篮球快讯
基于Redis的zset命令实现排行榜功能 并列排行榜 严格排行榜 日榜 周榜 月榜
发布时间:2024-06-01 01:10:01【篮球快讯】人次阅读
摘要 此栏是我开,此文是我写,要想读此文,把赞留下来。 不知道大家是不是和我一样,每次打开知乎,都要看一眼热榜,看看是否是有什么新闻,或者好玩的事情。 排行榜出现的越多,说明
此栏是我开,此文是我写,要想读此文,把赞留下来。
不知道大家是不是和我一样,每次打开知乎,都要看一眼热榜,看看是否是有什么新闻,或者好玩的事情。
排行榜出现的越多,说明其越重要。
从类型上来划分,排行榜主要分为两种,一种是并列排行榜,即存在相同排名的情况(如成绩排行榜);一种是严格排行榜,即分先后顺序,不存在并列名次(如游戏分数排行榜)。
当大家看排行榜的时候,是否想过排行榜功能是怎么实现的呢?如果没想过,现在是个机会,我们就以游戏分数行榜为例一起思考一下,一个排行榜应该具有哪些功能?
1、查看最分数最高的前N位是哪几位玩家,这是最基本的功能,也是最不能缺少的功能。
2、当一个新玩家出现时,一开始并不在排行榜中,因此需要向排行榜添加它的信息。
3、原来在排行榜中的玩家,由于开学、工作或者其他原因,不再玩游戏,导致分数不再上升,则需要修改其排名和分数。
4、查看某个玩家的排行是第几位,它的分数是多少。
6、排行榜中总共有多少记录。
如果是其他排行榜的话,可能还会有其他需求,如下:
7、在成绩排行榜中,可能会统计两个分数之间有多少人。
8、在成绩排行榜中,可能会统计分数为N的人数是多少。
首先要说明的的是,实现排行榜的技术肯定是多种多样,本文只聊聊通过Redis怎么来实现排行榜的功能。
Redis 的有序集合 Sorted Set中的成员是唯一的,但分数(score)却可以重复,这一点恰恰可以被我们用来实现排行榜的功能。向排行榜中添加一条记录(已存在则不更新)
基于StringRedisTemplate实现:
向排行榜中添加一条记录(已存在则更新)
基于StringRedisTemplate实现:
基于Jedis实现:
增加排行榜中某条记录的分数
基于StringRedisTemplate实现:
如果code不存在,相当于addIfAbsent。如果code存在,相等于在原来的分数的基础上再加上score。
incrementScore方法返回增加后的分数。
基于Jedis实现:
查看某条记录的分数
基于StringRedisTemplate实现:
如果code不存在,返回null。
基于Jedis实现:
统计排行榜中的记录数
基于StringRedisTemplate实现:
基于Jedis实现:
查看分数在M和N之间的记录有哪些
基于StringRedisTemplate实现:
基于Jedis实现:
查看排行榜中后N名(分数从低到高排序)
基于StringRedisTemplate实现:
基于Jedis实现:
查看排行榜中前N名(分数从高到低排序)
基于StringRedisTemplate实现:
基于Jedis实现:
查看某条记录的排名
基于StringRedisTemplate实现:
有序集合的索引是从0开始的,所以查询出来的排名要+1。
如果code不存在,则返回null。
基于Jedis实现:
从排行榜中删除某条记录
基于StringRedisTemplate实现:
基于Jedis实现:
我们在前言部分还提到了两种常见的场景,在讨论它们怎么实现之前,我们先讨论另一个问题:如果两个用户score相同,redis会如何排序呢?
其实,在score相同的情况下,redis使用的字典排序,就是根据字母和数字的顺序进行排序。
下面,我们通过一个例子验证一下。
我们在redis中执行如下命令。
从结果可以看出,在score相同的情况下,redis确实是用字典排序的。场景一:在植物大战僵尸无尽模式的积分排行榜中,用户得分越高,排行越靠前。如果分数相同,则先达成该分数的用户排在前面,如下图所示。对于这种场景,应该怎么实现?
针对这种场景,我们需要调整分数的结构。
由于需求是「分数相同,先达成该分数的用户排在前面」,换句话说,也就是「游戏分数相同的情况下,时间戳越小,排名越靠前」。
所以,一个比较容易想到的方法是将原来的分数结构改为分数和时间戳的结构。但是,这里有一个问题,原来是分数越大,排名越靠前;现在是时间戳越小,排名越靠前。
两者规则是相反的,所以,不同通过简单的组合解决问题。
怎么办呢?
诸位,莫慌!
在这情况下,我们可以指定一个非常大的时间MAX,如9999年12月31日23时59分59秒,将其转换为时间戳,然后减去达到指定分数的时间戳,这个差值越大,说明我们达成指定分数的时间越靠前,此时排名也应该靠前。
因此,分数结构就变成了分数 + "." + (MAX - 达成指定分数的时间戳),例如25000.10,其中25000是游戏得分,10是达到25000分时的时间的时间戳和MAX的差值。
那么,为什么我们不用分数 + (MAX - 达成指定分数的时间戳)的形式呢?
假设分数是25000,时间戳是10,两者相加是10,如果不仅要排序,还要展示分数的话,分数 + (MAX - 达成指定分数的时间戳)的形式就不行了。
而分数 + "." + (MAX - 达成指定分数的时间戳)的形式,可以将分数强制转化成整形,这样我们既可以排序,又可以展示分数了,一举两得!
时间戳就占了13位(自1970年以来的毫秒数),分数的位数太低也不好(一定程度上影响玩家的积极性。你想想,累死累活完了1年,一看分数才123(虽然排名挺高),想想这种场景吧)。
当然,如果我们只考虑排序的场景,那么得分和时间戳是个不错的选择,毕竟游戏分数至少设置的不超过13位,两者相加也不过14位,而且不用浮点数存储,不会有精度损失的问题。
但是,现在的问题是:在不但需要排序,还需要查看分数,Redis 又有存储浮点数精度丢失的问题的场景下,我们应该怎么办?
好难啊!
这种情况,只能分场景讨论了。
对于类似植物大战僵尸的无尽模式积分排行榜的周榜或者日榜(每日或者每周的排行榜都要从新开始计算),我们可以将MAX定的小一点,即可以将MAX设置为每个排行榜周期的下一天的开始。
例如,对于本周的植物大战僵尸的无尽模式积分排行榜,我们可以将MAX设置为2022-04-11 00:00:00对应的时间戳,本周开始时间是2022-04-04 00:00:00,则最大的差值也不过是604800000,只占9位。
对于月榜、季度榜,差值也不过占10位,年榜的话,差值则占11位。我能想到的也就是这种处理方法,如果大家有什么好的思路,可以在评论区交流!一定要不吝赐教啊!
下面我们通过代码实现一下「分数 + "." + (MAX - 达成指定分数的时间戳)」。
设置分数:
查询分数:
在有些业务场景中,不仅有日榜,还有周榜,甚至月榜的需求。在这种业务场景下,我们只需要按照最小的单位时间划分成不同的集合,最后求出这些集合的并集即可。
代码如下所示。
tv_ranking_week表示周榜的KEY,tv_rank_1表示周一那天的key,tv_rank_2表示周二那天的榜单。
还可以在合并各子榜的时候,设置权重和计算方式。
例如,设置第一个子榜的权重是0.5,第二个子榜的权重是0.8,计算方式是SUM(求和),则总榜的每个value的分数计算公式是:总榜某个value的分数 = 子榜一某个value的分数*0.5 + 子榜二某个value的分数*0.8。
计算方式除了SUM(求和),还有MIN(相同value下,取各个子榜中分数最小的那个)和MAX(相同value下,取各个子榜中分数最大的那个)。
这种通过子榜合并而生成总榜方式有一个问题:当子榜(tv_rank_1或者tv_rank_2)变更时,主榜(tv_ranking_week)并不会自动变更。
因此,这种方式不适合主榜时时变动的情景。一个可行的变动手段是,通过定时任务定时(5秒、10秒等)生成总榜。
所谓并列排行榜,就是存在相同排名情况的排行榜。比较常见的是我们上学时的「光荣榜」。
Redis的有序集合 Sorted Set好像并没有提供实现并列排行榜的功能的命令,在score相同的情况下,是根据vlaue的字典排序的。
所以,如果需要实现并列排行榜功能,还需要借助代码实现,以下是个例子。
通过下面的命令,向redis中添加学生的成绩,用于展示并列排行榜功能。
我们期望的结果一,如下表。姓名分数名次zheshishui1001nidaye862xiaolongnv773yangguo764lisi764limochou764wangermazi665fangshiyu665zhangwuji566zhangsan566
通过如下代码逻辑,对从Redis 中返回的数据进行处理。
我们期望的结果二,如下表。姓名分数名次zheshishui1001nidaye862xiaolongnv773yangguo764lisi764limochou764wangermazi667fangshiyu667zhangwuji569zhangsan569
通过如下代码逻辑,对从Redis 中返回的数据进行处理。
所谓并列排行榜,就是分先后顺序,不存在并列名次的排行榜。比较常见的是我们游戏分数排行榜,如我上面说的植物大战僵尸无尽模式的积分排行榜。
关于严格排行榜的实现方式,我们在上面已经给出了一个解决方案:「分数 + "." + (MAX - 达成指定分数的时间戳)」,这里就不再赘述。
如果只展示排序,通过「分数 + (MAX - 达成指定分数的时间戳)」的方式更简单。如果在排序的同时,还需要展示分数的话,「分数 + "." + (MAX - 达成指定分数的时间戳)」是个更好的选择。
在有些排行榜中(植物大战僵尸无尽模式的积分排行榜)还需要展示用户头像,其中一种方案是将头像的URL存放在value中,类似userid#url的格式。
标签:
很赞哦! ()
相关内容
有进步!库明加11中6得到16分2板1助三分7中4但罚球2中0
2024-10-11 10月10日讯 NBA季前赛,勇士以122-112击败国王。本场比赛,库明加首这引援真香!梅尔顿能攻能防12中6&三分9中4贡献16分5板3助
2024-10-11 10月10日讯 NBA季前赛,勇士122-113力克国王。本场比赛,勇士新援梅勇士28记三分!基根:我们有很多沟通错误防无球人身体对抗不足
2024-10-11 10月10日讯 NBA季前赛,国王以112-122不敌勇士。赛后,基根-穆雷接受希尔德接球三分再中勇士轰进25个三分NBA历史纪录是29个!
2024-10-11 第四节还剩10分多钟!注:季前赛纪录不纳入NBA常规纪录范畴