使用Redis实现游戏排行榜

排行榜在当今应用中扮演着至关重要的角色。无论是游戏中的玩家排名、社交平台的用户活跃度榜单,还是其他领域的各种榜单,排行榜都是用户参与性和互动性的关键。在实现排行榜功能时,选择合适的数据库和数据结构至关重要。Redis,作为一种内存数据库,以其高性能和灵活性而备受青睐。下面将探讨如何使用Redis的有序集合(Sorted Set)来实现排行榜功能。

1 Redis的有序集合

Redis是一种内存型数据库,查询效率高。Redis有一种数据结构叫有序集合(Sorted Set),与普通集合相比,它的每个成员都关联一个分数,这个分数用于对成员进行排序。有序集合在插入和查询时都能够以 O ( l o g n ) O(log{n}) O(logn)的复杂度完成,这为排行榜的实现提供了高效的基础。有序集合不仅提供了快速的插入和查询操作,还支持范围查询,使得获取某个范围内的排名成员变得非常简单。

(关于Sorted Set如何实现高效的插入和查询,可以看我的这篇文章《Redis的跳跃表》)

2 使用有序集合实现排行榜

那么,我们要如何用Sorted Set来实现排行榜呢?

其实非常简单,首先根据需求,我们定义一个有序集合的key,例如:

  • 玩家等级的排行榜,我们可以用rank:level来作key值。
  • 每天更新的排行榜,可以在后面加个日期rank:level:0412
  • 还有些比如是每天对指定BOSS的伤害排行,可以用rank:damage:bossID:0412

当排行数值改变的时候,我们用zadd指令来更新数据:

redis 复制代码
zadd rank:level 玩家等级 玩家ID #参数是`score`和`member`

也可以用

redis 复制代码
ZINCRBY rank:level 10 lxx1 #使lxx1的积分增加10(如果lxx1在rank:level中不存在,则新增,设置积分为10)

需要查询排行榜数据的时候,我们用zrevrange指令来获取数据(下标是从0开始):

zrevrange rank:level 0 99 WITHSCORES    # 获取前100名的ID和分数
zrevrange rank:level 100 199 WITHSCORES # 获取101-200名的ID和分数
zrevrange rank:level 0 99               # 仅获取ID

这里我们使用zrevrange,因为zset是按从小到大排序的,zrevrange是逆序返回zset中的数据。

取出玩家ID之后,我们再从另外的地方(MySQLredis或者内存中)获取玩家的其他数据(名字,头像等),组合出完整的榜单数据。

查看排行玩家的排行和数值:

redis 复制代码
zscore rank:level lxx1 #获取玩家lxx1的分数
zrevrank rank:level lxx1 #获取玩家lxx1的排名

如果要移除某个玩家的排行,可以使用zrem指令:

redis 复制代码
zrem rank:level 玩家ID

3 实现数值相同时,按时间先后排序

游戏排行榜中,经常有这样的需求:玩家等级相同时,按照到达这个等级的时间先后顺序排序。

使用Sorted Set时,我们可以将数值乘以一个系数,然后加上时间戳来实现这个功能。

比如,玩家等级27级,当前时间戳是1705589522,我们可以将这两个数组组合起来,由于时间戳是越小排序越前(和等级越大排序越前相反),我们使用相减的方式:

val = 27*1e10 + 1e10 - 1705589522

最终的结果是278294410478,其中前面的27表示等级,后面的8294410478是时间戳和1e10的差值(10位数的时间戳最大可以用到2086年,有生之年够用了),等级越大,这个值越大,而等级相同时,时间戳小的值更大。

值得注意的是,Sorted Set底层是使用double类型来存储数值,所以当排序的值过大时,加上这个时间戳可能就会不够精细。

通常来说,使用double能表示的精确的正整数可以达到 2 53 − 1 {2}^{53}-1 253−1(900719925474099116位数字)(关于这个值的计算,可以看我的另一篇文章《double 类型中可精确表达的最大正整数》)

不过对于排行榜而言,如果本身的数值已经很大了,通常也不需要按照时间来排序了。比如BOSS伤害十几亿,这时候玩家连具体数值可能都看不到,通常也不会相同,用不着时间先后排序了。

4 排行榜的合并

合服的时候,使用Sorted Set也可以很方便的合并排行榜。

Redis提供了并集(zunionstore)操作,并集指的是将两个或多个zset中的元素合并为一个新的zset

它的语法如下:

redis 复制代码
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]

其中destination表示合并到的目标keynumkeys表示后面有多少个key要用来合并。例如有三个key需要合并:rank:s1:levelrank:s2:levelrank:s3:level,我们可以这样写:

ZUNIONSTORE rank:s1:level 3 rank:s1:level rank:s2:level rank:s3:level

表示将三个key合并,然后存储到rank:s1:level这个key中。

不过需要注意的是,在Redis的集群模式下,这样操作有可能会报错。(具体看这里《Redis 报错:CROSSSLOT Keys in request don't hash to the same slot 的解决方案》)。

相关推荐
王佑辉10 分钟前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
Karoku0661 小时前
【企业级分布式系统】Zabbix监控系统与部署安装
运维·服务器·数据库·redis·mysql·zabbix
gorgor在码农1 小时前
Redis 热key总结
java·redis·热key
想进大厂的小王1 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情1 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
minihuabei6 小时前
linux centos 安装redis
linux·redis·centos
monkey_meng8 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
hlsd#9 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
奶糖趣多多11 小时前
Redis知识点
数据库·redis·缓存
CoderIsArt12 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存