利用redis实现排行榜的小秘诀

前言

排行榜作为互联网应用中几乎必不可少的一个元素,其能够勾起人类自身对比的欲望,从而来增加商品的销量。

对于排行榜的需求,redis有一个数据结构非常适合做这件事,那就是有序集合(sorted set)。

在日常一些简单的活动开发中,我经常会碰到需要对用户的分值等进行排行,此时一般会选择redis的有序集合对用户的分数进行存储,但是不同的场景排行榜的方式也略有不同,以下根据自己日常的开发进行了一下归纳总结

Redis 有序集合(sorted set)

首先简单介绍下什么是有序集合。

Redis 的Sorted Set 是 String 类型的有序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

应用场景

场景一:用户得分越高,排行越前面

这是一种最简单基本的应用场景,使用的命令和基本操作如下:

ZADD:添加or更新成员分数

命令参数:ZADD key score member [[score member] [score member] ...]

将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 member 元素,来保证该 member 在正确的位置上。

score 值可以是整数值或双精度浮点数。

如果 key 不存在,则创建一个空的有序集并执行 ZADD 操作。

当 key 存在但不是有序集类型时,返回一个错误。

示例:

// 假设用户A(user1)当前游戏的分数为50,则
ZADD user_rank 50 user1
// 添加用户B(user2)当前游戏的分数为60、用户C(user3)当前游戏的分数为70,则可批量操作
ZADD user_rank 60 user2 70 user3 // 同时添加user2、user3 两个用户的分数,分别为 2、3

ZREVRANK:获取成员当前的排名

命令参数:ZREVRANK key member

返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。

排名以 0 为底,也就是说, score 值最大的成员排名为 0 。

示例:

// 获取用户A当前的排名
ZREVRANK user_rank user1 // user1 当前排名为第三,则输出 2

ZSCORE:获取用户排名

命令参数:ZSCORE key member

返回有序集 key 中,成员 member 的 score 值。

如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil 。

示例:

// 获取用户A当前的排名
ZSCORE user_rank user1 // user1 当前分数为50,则输出 "50" #注意返回值是字符串

场景二:用户游戏中花费的时间最短,排行越前面

这也算一种最简单基本的应用场景,使用的命令和基本操作和场景一差不多,除了获取排名的命令不一样之外:

ZRANK:获取成员当前的排名

命令参数:ZRANK key member

返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。

排名以 0 为底,也就是说, score 值最小的成员排名为 0 。

如何处理以上两个场景中用户分数相同的情况

如果两个用户score相同,redis如何排序呢

在score相同的情况下,redis使用字典排序

那什么是字典排序呢?相信下图就可以解答到这个疑问

在score相同的情况下,redis使用字典排序,而所谓的字典排序其实就是“ABCDEFG”、"123456..."这样的排序,在首字母相同的情况下,redis会再比较后面的字母,还是按照字典排序

场景一:用户得分越高,排行越前面,如果分数相同情况下,先达成该分数的用户排前面

此场景下,我们需要更改用户的分数构成,具体思路如下:

  • 分数相同,用户完成游戏的时间戳也加入到score值的构成中
  • 先达成该分数的用户排前面,即游戏所得分数相同的情况下,时间戳越小,越排前
  • 如果我们简单地把score结构由:分数+''+时间戳 拼凑,因为分数越大越靠前,而时间戳越小则越靠前,这样两部分的判断规则是相反的,无法简单把两者合成一起成为用户的score
  • 但是我们可以逆向思维,可以用同一个足够大的数MAX减去时间戳,时间戳越小,则得到的差值越大,这样我们就可以把score的结构改为:分数+''+(MAX-时间戳),这样就能满足我们的需求了
  • 如果使用整数作为score,有一点需要注意的是,js中最大的整数为:

Math.pow(2, 53) - 1 // 9007199254740991 ,16位数

时间戳已经占用了13位数了,因此留给我们保存用户的真正分数的只剩下3位数了

所以最好使用双精度浮点数类型作为score

因此,最好的score结构为:分数+'.'+时间戳,变为浮点数

场景二:用户完成游戏时间最短,排行越前面,如果完成游戏时间相同情况下,先达到该记录的用户排前面

此场景下,我们也需要更改用户的score构成,具体思路如下:

  • 完成游戏时间相同,用户完成游戏的时间戳也加入到score值的构成中
  • 游戏时间相同,先达到该记录用户排前面,即游戏所得分数相同的情况下,时间戳越小,越排前
  • 游戏时间越小越靠前,而时间戳越小也越靠前,这样两部分的判断规则是一致的,我们可以把两者合一起拼凑成score:分数+'.'+时间戳 即可
  • 则用户score越小,用户排名越前

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • 使用Redis实现用户积分排行榜的教程

    排行榜功能是一个很普遍的需求.使用 Redis 中有序集合的特性来实现排行榜是又好又快的选择. 一般排行榜都是有实效性的,比如"用户积分榜".如果没有实效性一直按照总榜来排,可能榜首总是几个老用户,对于新用户来说,那真是太令人沮丧了. 首先,来个"今日积分榜"吧,排序规则是今日用户新增积分从多到少. 那么用户增加积分时,都操作一下记录当天积分增加的有序集合. 假设今天是 2015 年 04 月 01 日,UID 为 1 的用户因为某个操作,增加了 5 个积分. Re

  • redis实现排行榜的简单方法

    1 前言 实现一个排版榜,我们通常想到的就是mysql的order by 简单粗暴就撸出来了.但是这样真的优雅吗? 数据库是系统的瓶颈,这是众所周知的.如果给你一张百万的表,让你排序做排行榜,花费的时间是十分可怕的. 不如缓存吧,order by的时候强制使用索引.但是这样真的优雅吗? 2 Redis的排行榜 我们分析一下排行榜,一个用户一个排名,意味着要去重,这时我们会想到Java的一种数据结构Set.不过Set又是无序的.有没有一种结构是可以保住元素唯一以及有序的呢. 幸运的是,还真的有.R

  • 利用Redis的有序集合实现排行榜功能实例代码

    前言 游戏中存在各种各样的排行榜,比如玩家的等级排名.分数排名等.玩家在排行榜中的名次是其实力的象征,位于榜单前列的玩家在虚拟世界中拥有无尚荣耀,所以名次也就成了核心玩家的追求目标. 一个典型的游戏排行榜包括以下常见功能: 能够记录每个玩家的分数: 能够对玩家的分数进行更新: 能够查询每个玩家的分数和名次: 能够按名次查询排名前N名的玩家: 能够查询排在指定玩家前后M名的玩家. 更进一步,上面的操作都需要在短时间内实时完成,这样才能最大程度发挥排行榜的效用. 由于一个玩家名次上升x位将会引起x+

  • 基于redis实现世界杯排行榜功能项目实战

    题外话: 小编先给大家推荐一个不错的微信公众号: 感兴趣的朋友可以关注小编的微信公众号[码农那点事儿],更多网页制作特效源码及学习干货哦!!! 需求 前段时间,做了一个世界杯竞猜积分排行榜.对世界杯64场球赛胜负平进行猜测,猜对+1分,错误+0分,一人一场只能猜一次. 1.展示前一百名列表. 2.展示个人排名(如:张三,您当前的排名106579). 分析 一开始打算直接使用mysql数据库来做,遇到一个问题,每个人的分数都会变化,如何能够获取到个人的排名呢?数据库可以通过分数进行row_num排

  • 利用redis实现排行榜的小秘诀

    前言 排行榜作为互联网应用中几乎必不可少的一个元素,其能够勾起人类自身对比的欲望,从而来增加商品的销量. 对于排行榜的需求,redis有一个数据结构非常适合做这件事,那就是有序集合(sorted set). 在日常一些简单的活动开发中,我经常会碰到需要对用户的分值等进行排行,此时一般会选择redis的有序集合对用户的分数进行存储,但是不同的场景排行榜的方式也略有不同,以下根据自己日常的开发进行了一下归纳总结 Redis 有序集合(sorted set) 首先简单介绍下什么是有序集合. Redis

  • SpringBoot利用redis集成消息队列的方法

    一.pom文件依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 二.创建消息接收者 变量.方法及构造函数进行标注,完成自动装配的工作. 通过 @Autowired的使用来消除 set ,get方法. @Autowired pub

  • 如何利用Redis分布式锁实现控制并发操作

    redis命令解释 说道Redis的分布式锁都是通过setNx命令结合getset来实现的,在讲之前我们先了解下setNx和getset的意思,在redis官网是这样解释的 注:redis的命令都是原子操作 SETNX key value 将 key 的值设为 value ,当且仅当 key 不存在. 若给定的 key 已经存在,则 SETNX 不做任何动作. SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写. 可用版本: 1.0.0+ 时间复杂度: O(1)

  • redis实现排行榜功能

    redis的zset可以很方便地用来实现排行榜功能,下面简单介绍python如何使用redis实现排行榜功能 加入排行榜 获取redis实例 import redis main_rds = redis.StrictRedis(host='localhost',port=6379,db=0,password='') 全量加入排行榜 注意根据redis包的版本不同,zadd和zincby的传参方式也不同 对于redis 3.0(python的redis包)以下的版本 # 向key为name的zset

  • Springboot利用Redis实现接口幂等性拦截

    目录 前言 正文 实战开始 核心三件套 工具类三件套 REDIS配置类 前言 近期一个老项目出现了接口幂等性 校验问题,前端加了按钮置灰, 依然被人拉着接口参数一顿输出,还是重复调用了接口,小陈及时赶到现场,通过复制粘贴,完成了后端接口幂等性调用校验. 以前写过一篇关于接口简单限流防止重复调用的,但是跟该篇还是不一样的,该篇的角度是接口和参数整体一致才当做重复. 简单限流:Springboot使用redis实现接口Api限流的实例 该篇内容: 实现接口调用的幂等性校验 方案 :自定义注解+red

  • Redis实现排行榜及相同积分按时间排序功能的实现

    目录 不考虑积分相同 积分相同按时间排序,排名唯一 设计1 设计2 积分相同按时间排序,并列排名 在日常的开发中,经常会碰到需要对用户的分值等进行排序,比如在游戏里面需要对战斗力进行排行,在组队活动中需要对各个队伍的贡献值进行排行,在微信中需要对各个好友的步数进行排行,此时一般会选择redis的有序集合对用户的分数进行存储,从而实现排行榜的需求,但是不同的场景排行榜的方式也略有不同,以下根据自己日常的开发进行了一下归纳总结. 需求:对组队活动中各个队伍的贡献值进行排行. 不考虑积分相同 Redi

  • Java利用redis实现防止接口重复提交

    目录 一.摘要 二.方案实践 2.1.引入 redis 组件 2.2.添加 redis 环境配置 2.3.编写获取请求唯一ID的接口,同时将唯一ID存入redis 2.4.编写服务验证逻辑,通过 aop 代理方式实现 2.5.在相关的业务接口上,增加SubmitToken注解即可 三.小结 一.摘要 在上一篇文章中,我们详细的介绍了对于下单流量不算高的系统,可以通过请求唯一ID+数据表增加唯一索引约束这种方案来实现防止接口重复提交! 随着业务的快速增长,每一秒的下单请求次数,可能从几十上升到几百

  • 利用Redis实现SQL伸缩的方法

    这篇文章主要介绍了利用Redis实现SQL伸缩的方法,包括讲到了锁和时间序列等方面来提升传统数据库的性能,需要的朋友可以参考下. 缓解行竞争 我们在Sentry开发的早起采用的是sentry.buffers. 这是一个简单的系统,它允许我们以简单的Last Write Wins策略来实现非常有效的缓冲计数器. 重要的是,我们借助它完全消除了任何形式的耐久性 (这是Sentry工作的一个非常可接受的方式). 操作非常简单,每当一个更新进来我们就做如下几步: 创建一个绑定到传入实体的哈希键(hash

  • Java利用Redis实现消息队列的示例代码

    本文介绍了Java利用Redis实现消息队列的示例代码,分享给大家,具体如下: 应用场景 为什么要用redis? 二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成java对象; 主要是用到了ByteArrayOutputStream和ByteArrayInputStream; 注意:每个需要序列化的对象都要实现Serializable接口; 其代码如下: package Utils

随机推荐