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

用Redis怎么快速搞定排行榜前十名的查找和取数问题

要快速搞定排行榜前十名的查找和取数问题,Redis绝对是首选利器,因为它天生就是干这个的,核心思路就是用Redis的有序集合这个数据结构,你可以把有序集合想象成一个榜单,里面的每个成员(比如用户ID或者游戏玩家ID)都对应一个分数(比如用户的积分、游戏的得分),而且这个集合会自动根据分数从大到小或者从小到大排好序,这正是我们做排行榜需要的。

具体怎么做呢?我们从几个方面来直接说清楚。

第一,用什么数据结构?就用ZSET。

在Redis里,有序集合的命令都是以Z开头的,最关键的几个操作是:

  • 添加或更新分数ZADD key score member,用户user1得了100分,你就执行ZADD leaderboard 100 user1,如果user1后来又得了50分,你再执行ZADD leaderboard 50 user1,Redis会很聪明地把他的分数更新为150分(默认是累加,你也可以设置成覆盖)。
  • 查看排名ZREVRANK key member,这个命令能立刻告诉你某个成员在榜单里排第几名,注意,ZREVRANK是倒序排列,也就是分数从高到低排,排名从0开始,所以查询第一名返回的是0,如果你想看正序(分数从低到高)就用ZRANK,但排行榜一般用倒序。
  • 查看分数ZSCORE key member,这个命令能立刻返回某个成员的具体分数是多少。

第二,怎么快速取前十名?一个命令搞定。

这是最核心的优势,当你把所有用户和分数都用ZADD添加进去之后,整个集合已经在内存里按分数排好序了,这时候,你要取前十名,只需要一个命令: ZREVRANGE key 0 9 WITHSCORES 这个命令的意思是:在名为leaderboard的这个有序集合里,进行倒序排列(分数高的在前),然后取出从第0位到第9位(也就是前十名)的成员,并且把他们的分数也一起返回。 这个操作的速度非常快,是常数时间复杂度,也就是说,无论你的排行榜里有1万人还是1亿人,查询前十名所花的时间基本是一样的,因为Redis直接读取的是已经排好序的数据结构的头部,不需要临时计算。

用Redis怎么快速搞定排行榜前十名的查找和取数问题

第三,怎么处理相同分数的情况?

有时候会出现两个用户分数一样,那他们的排名怎么算?Redis的处理规则是:如果分数相同,则按照成员的字典序(lexicographical order)来排列,简单说,就是比较字符串的ASCII码,apple"会排在"banana"前面。 对于大多数排行榜应用来说,这个规则是可以接受的,如果你有特殊需求,比如希望分数相同的用户按照达到该分数的时间先后排序,你可以在设计分数时做点手脚,一个常见的技巧是:把实际分数和一个时间戳组合成一个新分数。 你的分数是整数,可以用一个非常大的数(比如10的10次方)减去时间戳(精确到毫秒),然后把这个计算出来的数字作为小数部分,加在实际分数后面。 假设用户A和用户B都得了100分,但A比B早达到,你可以这样: 用户A的分数做成:9999999999 (这里用9999999999模拟一个“大数-时间戳”的值,实际值会更长) 用户B的分数做成:0000000001 这样,虽然整数部分都是100,但因为A的小数部分更大,在Redis排序时,A依然会排在B的前面,取数的时候,你只需要取整数部分就是真实分数了,这个方法来自Redis官方文档和社区的最佳实践。

第四,除了前十名,还能做什么?

用Redis怎么快速搞定排行榜前十名的查找和取数问题

有序集合的功能很强大,除了查前十名,你还能轻松实现很多相关需求:

  • 查看自己的排名和分数:用前面说的ZREVRANKZSCORE
  • 查看前十名附近的人:比如你想看第8名到第12名,用ZREVRANGE leaderboard 7 11 WITHSCORES就行了。
  • 查看某个分数段的用户:比如你想看看所有积分在500分到800分之间的玩家,可以用ZRANGEBYSCORE命令。 这些操作都非常高效。

第五,一个简单的例子

假设我们做一个游戏得分排行榜。

  1. 玩家1(uid:1001)得50分:ZADD game_rank 50 uid:1001
  2. 玩家2(uid:1002)得80分:ZADD game_rank 80 uid:1002
  3. 玩家3(uid:1003)得30分:ZADD game_rank 30 uid:1003
  4. 玩家1又得了20分:ZADD game_rank 20 uid:1001 (此时uid:1001总分为70) 现在榜单里有三个玩家,分数分别是80,70,30。
  5. 查询前十名(虽然现在只有三个):ZREVRANGE game_rank 0 9 WITHSCORES Redis会返回:
    1. "uid:1002" (第一名)
    2. "80"
    3. "uid:1001" (第二名)
    4. "70"
    5. "uid:1003" (第三名)
    6. "30"

总结一下 用Redis做排行榜,核心就是使用有序集合(ZSET)

  • 更新数据ZADD,又快又方便,还能自动更新分数。
  • 取前十名ZREVRANGE 0 9 WITHSCORES,一个命令瞬间完成,速度与数据量无关。
  • 处理并列排名可以利用“分数+时间戳”组合的技巧。 这种方法简单、直接、高效,是经过大量实践检验的方案,你不需要关心底层是怎么排序和存储的,Redis都帮你做好了优化,你只需要调用几个简单的命令就行。