排行榜的简单设计思路

Posted leestar54

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排行榜的简单设计思路相关的知识,希望对你有一定的参考价值。

TOC

前言

技术图片

排行榜几乎已经成为互联网应用中的必备模块,特别是游戏领域,它是对某一相关同类事物的客观实力的反映,带有相互之间的比较性质,带有竞争意义。
对于平台来说,可以带来一定的权威性,提高平台影响力。
对于商家来说,可以带来更多的曝光,并对比自身的不足加以改进。
对于用户来说,可以为行动决策做参考,降低相关风险成本。
那么排行榜如何实现?我将结合自身的经验提供一些简单设计思路。

基于mysql

SELECT ORDER BY

对于小型的低频业务系统,mysql已经可以支撑所有的排序需求,类似班级排名,成绩排名都比较好实现。
直接SELECT ORDER BY即可。

SELECT name, score, @rank := @rank + 1 AS rank
FROM device, ( SELECT @rank := 0 )
ORDER BY score DESC

其中@rank := 0是为了在生成查询结果表的时候生成一组递增的序列号

mysql中:==的区别
=
只有在set和update时才是和:=一样,用于赋值,其它都情况用作等于判断,1表示真,0表示假。
:=
用于变量赋值

需要注意的是,该sql语句会进行全表的扫描,所以对于大表来说就不适用了。

加索引

一般来说,在数据比较多的时候,排行榜中我们一般不会进行全表排名,否则成本开销会很大,所以会按前100、500、1000这样来排名。此时我们只要在需排序字段加个索引,然后limit即可。

SELECT name, score, @rank := @rank + 1 AS rank
FROM device, ( SELECT @rank := 0 )
ORDER BY score DESC
LIMIT 100

这样mysql就会优先走索引,避免了全表扫描。

加缓存

对于mysql来说,索引的增删查改也是需要维护的,所以如果对于需要频繁修改的排序字段,并且是非实时的排序需求(例如按小时、按天、按月等),我们可以考虑在写入前加缓存,避免频繁操作数据库,影响其性能。
例如:一分钟可能需要增减score字段值50次,那么我们可以由缓存先接手请求,等一分钟后,再统一写入数据库,那么这一分钟数据库操作的次数就少了50次,另外读取排行的时候也可以加缓存,效果显著。
当然缓存期越长,提升越多,但是也要考虑到缓存失效导致数据丢失等情况,来保证数据的一致性。

借助redis

对于非实时的排行来说,mysql+缓存是可以支撑业务,但是如果需要实时的排行,mysql就力不从心了,此时我们需要基于高性能的内存来进行操作。
没错,redis大兄弟又登场了,借助redis的高性能、原子性、以及丰富的数据结构,能够帮助我们快速实现许多传统数据库很难完成的事。
这次我们需要借助到的是redis的有序集合(sorted set)

有序集合是通过包含跳表和哈希表的双端口数据结构实现的,因此,每次添加元素时,Redis的复杂度都是O(log(N))。当我们要求排序的元素时,Redis根本不需要做任何工作,它已经全部排序了。

这里我们主要用到一下几个命令

ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员,或者更新已存在成员的分数


ZINCRBY key increment member
有序集合中对指定成员的分数加上增量 increment


ZRANK key member
返回有序集合中指定成员的索引


ZRANGE key start stop [WITHSCORES]
通过索引区间返回有序集合指定区间内的成员


ZREMRANGEBYRANK key start stop
移除有序集合中给定的排名区间的所有成员

实时排行榜

由于用户的score数据还是需要记录保存的,所以上述的mysql方案依旧需要执行,只不过实时部分由redis接手。
以前100名实时数据为例。
1、读取mysql中排名前100,写入redis有序集合中。
2、当数据发生变动

  • 判断score是否低于最后一名,是则直接忽略。
  • 写入redis有序集合中。
    3、定期移除排名外的元素。

实现原理

redis有序集合实现的核心数据结构是【跳表】,大概长这样
技术图片

对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是 O(n)。
如果像图中那样,对链表建立一级“索引”,查找起来是不是就会更快一些呢?每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作索引或索引层。
当链表的长度 n 比较大时,比如 1000、10000 的时候,在构建索引之后,查找效率的提升就会非常明显。
这种链表加多级索引的结构,就是跳表

熟悉JAVA的同学肯定知道,hashmap是基于数组+链表的方式,当链表大小超过阀值之后,会变为红黑树来提高效率。

为什么 Redis 要用跳表来实现有序集合,而不是红黑树?

Redis 中的有序集合支持的核心操作主要有下面这几个:
插入一个数据;
删除一个数据;
查找一个数据;
按照区间查找数据(比如查找值在 [100, 356] 之间的数据);
迭代输出有序序列。
其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。
对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。
Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。
不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。

参考

https://redis.io/topics/data-types-intro
https://redis.io/commands#sorted_set
https://time.geekbang.org/column/article/42896










































以上是关于排行榜的简单设计思路的主要内容,如果未能解决你的问题,请参考以下文章

Android排行榜 记录

Python高级应用程序设计任务

带老弟做个实时排行榜

带老弟做个实时排行榜

带老弟做个实时排行榜

爬取当当网新书排行及数据分析