当前位置:首页 > 问答 > 正文

Redis怎么让排行榜变得又快又准,排名计算其实没那么难

知乎专栏“技术琐话”、掘金小册“Redis深度实战”以及个人项目实践经验)

Redis让排行榜又快又准,核心在于它内置了一个叫做“有序集合”的数据结构,你可以把它想象成一个特别的班级花名册,这个花名册不是按学号也不是按姓氏笔画排的,而是按一个具体的分数来排序的,每个同学的名字在花名册里只出现一次,但他们的分数可以随时改变,而且花名册会自动地、瞬间地根据新的分数把所有人的名次重新排好。

这个“有序集合”就是排行榜的魔法引擎,它快,是因为所有排序操作都是在内存里完成的,不像数据库要读写硬盘,内存的速度是硬盘的成千上万倍,它准,是因为它的排序规则非常简单粗暴:分数高的排前面,分数一样的,再按名字的字典顺序排,这个规则是确定的,不会出现模棱两可的情况。

具体怎么用呢?我们用游戏排行榜来举个例子,就完全清楚了。

你要把玩家的ID和对应的分数放进去,这就像给花名册登记信息,在Redis里,命令是ZADD,玩家“张三”得了1000分,玩家“李四”得了1500分,你就执行:

ZADD leaderboard 1000 "张三"
ZADD leaderboard 1500 "李四"

这样,数据就存进去了,这个leaderboard就是我们这个排行榜的名字,你可以随便起。

最核心的来了,怎么获取排名?这里有两个非常常用的方法:

第一种,想知道前10名是谁。 这就像老师想看看这次考试班级前十名,Redis的命令是ZREVRANGE,注意名字里有个“REV”,是“反转”的意思,因为默认是从小到大排,我们排行榜需要从大到小排,所以用这个命令,你执行:

ZREVRANGE leaderboard 0 9 WITHSCORES

这个命令的意思是:在leaderboard这个排行榜里,取出排名从0到9(也就是第1名到第10名)的所有人,并且把他们的分数也一起显示出来(WITHSCORES),结果立刻就会返回,李四1500分第一,张三1000分第二,哪怕你现在有100万个玩家,查前10名也还是这么快,因为它不是临时去计算的,而是这个“花名册”本身就一直维持着这个顺序。

第二种,想知道某个玩家具体排第几名。 比如张三想知道自己排第几,命令是ZREVRANK,你执行:

ZREVRANK leaderboard "张三"

Redis会立刻告诉你一个数字,1”,注意,这个排名是从0开始的,所以返回1表示第二名,这个查询速度同样极快。

光有基础的排名还不够,一个成熟的排行榜还需要处理一些特殊情况,而Redis的有序集合都能轻松应对:

分数相同怎么办? 就像考试并列第二,那第三名应该是谁?Redis的规则是,如果分数相同,就再按照成员的名字(我们存的是玩家ID)的字典顺序来排,比如王五也得了1500分,他和李四分数一样,那么就会比较“李四”和“王五”这两个字符串的顺序,这个规则是固定的,所以不会产生混乱,保证了“准”。

如何实现实时更新? 游戏里玩家分数是随时变化的,这太简单了,再用一次ZADD命令就行了,比如张三又玩了一局,加了200分,总分变成1200分,你只需要执行:

ZADD leaderboard 1200 "张三"

因为张三已经存在在集合里了,这个操作会自动更新他的分数,最关键的是,在他分数更新的那一瞬间,整个排行榜的次序就已经自动重新排列好了,你后面再用ZREVRANGE或者ZREVRANK去查,看到的就是最新的、完全正确的排名,这个“自动排序”的特性,让我们完全不用自己写复杂的排序逻辑,省去了巨大的麻烦。

如何给排行榜加上时间限制?比如日榜、周榜? Redis本身不直接存时间,但我们可以用一点小技巧,一个非常常见的做法是,给排行榜的Key(也就是我们例子里的leaderboard)加上时间后缀。

  • 日榜leaderboard:20240527
  • 周榜:可以按年份和周数来拼,比如leaderboard:202421

这样,每天零点,你的程序就创建一个新的Key(如leaderboard:20240528)来记录新的一天的数据,查询日榜的时候,就去查当天的Key,而历史的数据就放在那里,如果你需要月榜,可以通过程序把一个月内每天的数据汇总计算出来(这个过程可以放在后台慢慢算,不影响实时排名查询的速度),这种方法简单又高效。

数据量太大,内存不够怎么办? 这确实是个现实问题,因为Redis的数据都在内存里,解决办法也有,对于过期的排行榜,比如一个月前的日榜,如果确定不再需要,可以直接删除Key来释放内存,如果连历史数据也需要保留查询,可以考虑将冷数据(不常访问的数据)从Redis中导出,存到硬盘数据库(比如MySQL)里,需要时再从数据库查,这叫“冷热数据分离”,保证了Redis只存放最需要高速访问的热点数据。

总结一下,Redis通过其内置的“有序集合”数据结构,把复杂的排序和排名计算问题,变成了几个简单命令的调用,我们不需要关心底层是怎么实现排序的,只需要告诉Redis“把分数加进去”、“把前N名给我”、“告诉XXX排第几”,它就能毫秒级地返回准确结果,这种“把专业的事交给专业的工具”的思想,正是让我们开发变得轻松、让系统变得“又快又准”的秘诀。

Redis怎么让排行榜变得又快又准,排名计算其实没那么难