Skip to content

排行榜到处可见,比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜等等。

今天让我们从程序设计的角度,来看看如何设计一个排行榜!

我们先从最基础的实现方式来说起。

MySQL 的 ORDER BY 关键字

第一种要介绍的实现方式就是直接使用 MySQL 的 ORDER BY 关键字。 ORDER BY 关键字可以对查询出来的数据按照指定的字段进行排序。

我相信但凡是学过 MySQL 的人,一定都用过 ORDER BY 关键字!没用过的,先不要看下面的文章了,麻烦默默反思 3 分钟。

我之前在一个用户数据量不大(6w 用户左右)并且排序需求并不复杂的项目中使用的就是这种方法。

这种方式的优缺点也比较明显。好处是比较简单,不需要引入额外的组件,成本比较低。坏处就是每次生成排行榜都比较耗时,对数据库的性能消耗非常之大,数据量一大,业务场景稍微复杂一点就顶不住了。

我们这里创建一个名为 cus_order 的表,来实际测试一下这种排序方式。为了测试方便, cus_order 这张表只有 idscorename这 3 个字段。

我们定义一个简单的存储过程(PROCEDURE)来插入 100w 测试数据。

存储过程定义完成之后,我们执行存储过程即可!

等待一会,100w 的测试数据就插入完成了!

为了能够对这 100w 数据按照 score 进行排序,我们需要执行下面的 SQL 语句。

为了能够查看这套 SQL 语句的执行时间,我们需要通过show profiles命令。

不过,请确保你的 profiling 是开启(on)的状态(可以通过 show variables 命令查看)。

默认情况下, profiling 是关闭(off)的状态,你直接通过set @@profiling=1命令即可开启。

然后,我们就查询到了具体的执行速度。

可以看到,一共耗时了接近 4 s。

如何优化呢? 加索引并且限制排序数据量 是一种比较常见的优化方式。

我们对 score 字段加索引,并限制只排序 score 排名前 500 的数据。

这个时候,我们再执行下面的 SQL 语句,速度就快了很多,只需要 0.01 秒就排序了前 500 名的数据。

当然了,这只是一个最简单的场景,实际项目中的复杂度要比我这里列举的例子复杂很多,执行速度也会慢很多。

不过,能不用 MySQL 的 ORDER BY 关键字还是要看具体的业务场景。如果说你的项目需要排序数据量比较小并且业务场景不复杂的话(比如你对你博客的所有文章按照阅读量来排序),我觉得直接使用 MySQL 的 **ORDER BY** 关键字就可以了。

Redis 的 sorted set

了解过 Redis 常见数据结构的小伙伴,都知道 Redis 中有一个叫做 sorted set 的数据结构经常被用在各种排行榜的场景下。

通过 sorted set ,我们能够轻松应对百万级别的用户数据排序。这简直就是专门为排行榜设计的数据结构啊!

Redis 中 sorted set 有点类似于 Java 中的 TreeSetHashMap 的结合体,sorted set 中的数据会按照权重参数 score 的值进行排序。

User

Score

user1

112.0

user2

100.0

user3

123.0

user4

100.0

user5

33.0

user6

993.0

我们这里简单来演示一下。我们把上表中的数据添加到sorted set中。

sorted set 基本可以满足大部分排行榜的场景。

如果我们要查看包含所有用户的排行榜怎么办? 通过 ZRANGE (从小到大排序) / ZREVRANGE (从大到小排序)

如果我们要查看只包含前 3 名的排行榜怎么办? 限定范围区间即可。

如果我们需要查询某个用户的分数怎么办呢? 通过 ZSCORE 命令即可。

如果我们需要查询某个用户的排名怎么办呢? 通过 ZREVRANK 命令即可。

如何对用户的排名数据进行更新呢? 通过 ZINCRBY命令即可。

除了我上面提到的之外,还有一些其他的命令来帮助你解决更多排行榜场景的需求,想要深入研究的小伙伴可以仔细学习哦!

不过,需要注意的一点是:Redis 中只保存了排行榜展示所需的数据,需要用户的具体信息数据的话,还是需要去对应的数据库(比如 MySQL)中查。

你以为这样就完事了? 不存在的!还有一些无法仅仅通过 Redis 提供的命令解决的场景。

比如,如何实现多条件排序? 其实,答案也比较简单,对于大部分场景,我们直接对 score 值做文章即可。

更具体点的话就是,我们根据特定的条件来拼接 score 值即可。比如我们还要加上时间先后条件的话,直接在score 值添加上时间戳即可。

再比如,如何实现指定日期(比如最近 7 天)的用户数据排序?

我说一种比较简单的方法:我们把每一天的数据都按照日期为名字,比如 20350305 就代表 2035 年 3 月 5 号。

如果我们需要查询最近 n 天的排行榜数据的话,直接 ZUNIONSTORE来求 n 个 sorted set 的并集即可。

我不知道大家看懂了没有,我这里还是简单地造一些数据模拟一下吧!

通过 ZUNIONSTORE 命令来查看最近 3 天的排行榜情况:

现在,这 3 天的数据都集中在了 last_n_days 中。

如果一个用户同时在多个 sorted set 中的话,它最终的 score 值就等于这些 sorted set 中该用户的 score 值之和。

既然可以求并集,那必然也可以求交集。你可以通过 ZINTERSTORE 命令来求多个 n 个 sorted set 的交集。

有哪些场景可以用到多个**sorted set** 的交集呢? 比如每日打卡的场景,你对某一段时间每天打卡的人进行排序。

这个命令还有一个常用的权重参数 weights (默认为 1)。在进行并集/交集的过程中,每个集合中的元素会将自己的 score *weights

我下面演示一下这个参数的作用。

如果,我们需要将员工和管理者放在一起比较,不过,两者权重分别为 1 和 3。

最终排序的结果如下:

总结

上面我一共提到了两种设计排行榜的方法:

  1. MySQL 的 ORDER BY 关键字
  2. Redis 的 sorted set

其实,这两种没有孰好孰坏,还是要看具体的业务场景。如果说你的项目需要排序数据量比较小并且业务场景不复杂的话(比如你对你博客的所有文章按照阅读量来排序),我觉得直接使用 MySQL 的 ORDER BY 关键字就可以了,没必要为了排行榜引入一个 Redis。

另外,在没有分页并且数据量不大的情况下,直接在前端拿到所有需要用到的数据之后再进行排序也是可以的。

文章来源于自己总结和网络转载,内容如有任何问题,请大佬斧正!联系我