使用Redis实现游戏排行榜

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

1 Redis的有序集合

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

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

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

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

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

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

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

1
zadd rank:level 玩家等级 玩家ID #参数是`score`和`member`

也可以用

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

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

1
2
3
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或者内存中)获取玩家的其他数据(名字,头像等),组合出完整的榜单数据。

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

1
2
zscore rank:level lxx1 #获取玩家lxx1的分数
zrevrank rank:level lxx1 #获取玩家lxx1的排名

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

1
zrem rank:level 玩家ID

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

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

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

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

val = 27*1e10 + 1e10 - 1705589522

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

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

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

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

4 排行榜的合并

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

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

它的语法如下:

1
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 的解决方案》)。


使用Redis实现游戏排行榜
https://blog.supersource.top/implementing_rank_using_redis/
作者
看热闹的咸鱼
发布于
2024年4月12日
许可协议