pursue wind pursue wind
首页
Java
Python
数据库
框架
Linux
中间件
前端
计算机基础
DevOps
项目
面试
书
关于
归档
MacOS🤣 (opens new window)
GitHub (opens new window)
首页
Java
Python
数据库
框架
Linux
中间件
前端
计算机基础
DevOps
项目
面试
书
关于
归档
MacOS🤣 (opens new window)
GitHub (opens new window)
  • 技术面试题篇

    • 系统设计

      • 如何准备系统设计面试?
      • 如何统计网站UV?
      • 如何自己实现一个 RPC 框架?
      • 如何解决大文件上传问题?
      • 如何设计一个排行榜?
        • MySQL 的 ORDER BY 关键字
        • Redis 的 sorted set
        • 总结
      • 如何设计一个短链系统?
      • 如何设计一个秒杀系统?
      • 如何设计一个站内消息系统?
    • Java

    • 数据库

    • 常见框架

    • 分布式&微服务

    • 高并发

    • 服务器

    • 数据结构

    • 算法

  • 面试准备篇

  • 技术面试题自测篇

  • 练级攻略篇

  • 工作篇

  • 面经篇

  • 笑傲Java面试

  • LeetCode

  • 面试
  • 技术面试题篇
  • 系统设计
pursuewind
2020-11-22
目录

如何设计一个排行榜?

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

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

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

# MySQL 的 ORDER BY 关键字

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

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

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

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

我们这里创建一个名为 cus_order 的表,来实际测试一下这种排序方式。为了测试方便, cus_order 这张表只有 id、score、name这 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 中的 TreeSet 和 HashMap 的结合体,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。

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

Last Updated: 2023/01/30, 11:01:00
如何解决大文件上传问题?
如何设计一个短链系统?

← 如何解决大文件上传问题? 如何设计一个短链系统?→

Theme by Vdoing | Copyright © 2019-2023 pursue-wind | 粤ICP备2022093130号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
  • 飙升榜
  • 新歌榜
  • 云音乐民谣榜
  • 美国Billboard榜
  • UK排行榜周榜
  • 网络DJ