这是why哥的第89篇原创文章
前两天,有一个读者给我发了一张图片。
我问:发什么肾么事了?
于是有了这样的对话:
他发的图,就是微信运动步数排行榜的截图:
其实扯了这么多,这就是个常见的面试场景题:如何设计一个排行榜?
这个题吧,其实就是考你面试准备范围的广度,见过就会答,没见过...就难说了。
当然,如果你在实际业务中做过排行榜,那么这题正中下怀,你也不要笑出声来,场景题面试官是会给你思考时间的。
所以你不要张口就来,你只需要眉头稍稍一皱,给面试官说:这题我想想啊。
然后稍微组织一下语言,说出来就行。
这次的文章,就带着大家分析一下“排行榜”这个场景题,到底应该怎么做。
基于数据库
这个题,如果是真的之前没有遇见过,可能最容易进入大家视野的就是平时接触的最多的数据库了。
因为一想到“排行榜”,就想到了 order by。
一想了 order by,就想到了数据库。
一想到了数据库...
兄弟,你路就走窄了。
虽然我曾经就基于 MySQL 做过排行榜,因为当时是为了一个比赛临时搭建的服务,根本就没有引入 Redis。我评估了一下搭建 Redis 的时间和用 MySQL 直接开发的时间。
于是选择了 MySQL。
而让我选择 MySQL 的根本原因还是我已经知道进入决赛的队伍只有 10 支,也就是说我的排行榜表里面从始至终也只有 10 条数据。
选手提交代码之后,系统实时算分,然后更新排行榜表。
然后接口返回给前端页面下面这些数据,而下面这些数据都在一个表里面:
- 队伍按照历史最高分数排名
- 队伍名称
- 历史最高分数
- 最近一次提交得分
- 最近一次提交时间
前端每隔一分钟调用我的接口,相同分数,名次相同,所以我在接口里面用一条比较复杂的 sql 去查询数据库,上面的这些字段就都有了。
你看,排行榜确实是可以用 MySQL 来做的。
不一定非得上 Redis,记住一句话:脱离业务场景的方案设计,都是耍流氓。
但是这玩意和“万物皆对象”一样,别对着面试官说,这一定不是面试官想要听到的答案。
或者说,这只是想要听到的一部分回答。
这个回答能用的原因是我给了一个具体的场景,用户量非常的小,怎么玩都可以。
甚至我们不借助 MySQL 的排序,把数据查出来,在内存里面排序都可以。
但是如果,这是一个游戏排行榜,随着游戏玩家的增加,达到千万用户级别的话,这个方案肯定是不行了。
当然,也许你会给我扯什么查询慢就加索引,数据量大就分库分表的方案。
怎么说呢,上面这句话是没有错的。
但是一旦数据量大起来了,这个方案其实就不是一个特别好的方案。
这问题,得从根上治理。
基于 Redis
这个场景其实就是在考察你对于 Redis 的 sorted set 数据结构的掌握。
sorted set,见名知意,就是有序集合的意思。
前面的 sport:ranking:20210227 是 Redis 中的 key。
value 是一个集合,且可以看出这个集合是有序的。集合中的每一个 member 都有一个 score,然后按照这个 score 进行降序排序。
需要注意的是,图片中的 score/member 不是我随便写的,官网上就是这样定义的:
https://redis.io/commands/zadd#sorted-sets-101
在 Redis 中它大概是长这样的:
而且官网上说的是: score / member pairs。
所以我画图的时候,score 在前,member 在后。这可不是随便画的,虽然谁前谁后好像也不影响什么玩意。
另一个需要注意的点是,虽然我的示意图中没有体现出来,但是在有序集合中,元素即 member 是不可以重复的,但是 score 是可以重复的。
这个很好理解,就比如 20210227 这一天的微信步数,我可以走 6666 步,你也可以走 6666 步,这个是不冲突:
但是,问题就随之而来了:当 member 的 score 一样的时候,member 是怎么排序的呢?
看一下来自官网的答案:
当多个元素具有相同的分数时,它们按照 lexicographically 进行排序。
哎呀,lexicographically 这个单词不认识。
不慌,你知道的 why哥还兼职教英文:
当分数一样的时候,按照字典序排序,所以上面的示意图 jay 在 why 之前。
接下来,看一下有序集合的操作函数,一共有 32 个:
我这里就不一个个的做 API 教学了,官网上已经写的很清楚了,如果对于不熟悉的命令,可以去官网上查看,都是有示例代码的。
https://redis.io/commands/zadd#sorted-sets-101
比如这个 ZADD 方法: