mysql排行榜制作思路_排行榜的简单设计思路

本文介绍了如何使用MySQL和Redis来实现排行榜功能。对于小型系统,可通过MySQL的SELECT ORDER BY直接排序;大表场景下,利用索引和LIMIT优化查询。针对实时需求,引入Redis的有序集合(sorted set),利用ZADD、ZINCRBY等命令实现高效操作。同时讨论了缓存和跳表在排行榜中的作用,以及如何通过离线计算实现‘超过全国XX%的用户’提示。
摘要由CSDN通过智能技术生成

TOC

前言

37cbd4a2121d3f7de1e41c15259fc9b4.png

排行榜几乎已经成为互联网应用中的必备模块,特别是游戏领域,它是对某一相关同类事物的客观实力的反映,带有相互之间的比较性质,带有竞争意义。

对于平台来说,可以带来一定的权威性,提高平台影响力。

对于商家来说,可以带来更多的曝光,并对比自身的不足加以改进。

对于用户来说,可以为行动决策做参考,降低相关风险成本。

那么排行榜如何实现?我将结合自身的经验提供一些简单设计思路。

基于mysql

SELECT ORDER BY

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

直接SELECT ORDER BY即可。

SELECT name, score, @rank := @rank + 1 AS rank

FROM test, ( SELECT @rank := 0 )

ORDER BY score DESC

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

mysql中:=和=的区别

=

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

:=

用于变量赋值

如果想要计算排第几名就找出他分数高的有几个,再加上自身个数就好了。

SELECT count(1)+1 as '排名'

FROM test

WHERE score>(SELECT score FROM test WHERE name='李四')

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

加索引

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

SELECT name, score, @rank := @rank + 1 AS rank

FROM test, ( 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

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

ZREVRANK key member

返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

ZRANGE key start stop [WITHSCORES]

通过索引区间返回有序集合指定区间内的成员

ZREMRANGEBYRANK key start stop

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

实时排行榜

由于用户的score数据还是需要记录保存的,所以上述的mysql方案依旧需要执行,只不过实时部分由redis接手。

以前100名实时数据为例。

1、若集合未初始化,读取mysql中排名前100,写入redis有序集合中。

2、当数据发生变动

判断score是否低于最后一名,是则直接忽略。

写入redis有序集合中。

3、实时排名直接从redis读

4、定期移除排名外的元素。

实现原理

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

d763dd8c05695ad5a7e3a9634c874f5b.png

对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。这样查找效率就会很低,时间复杂度会很高,是 O(n)。

如果像图中那样,对链表建立一级“索引”,查找起来是不是就会更快一些呢?每两个结点提取一个结点到上一级,我们把抽出来的那一级叫作索引或索引层。

当链表的长度 n 比较大时,比如 1000、10000 的时候,在构建索引之后,查找效率的提升就会非常明显。

这种链表加多级索引的结构,就是跳表。

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

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

Redis 中的有序集合支持的核心操作主要有下面这几个:

插入一个数据;

删除一个数据;

查找一个数据;

按照区间查找数据(比如查找值在 [100, 356] 之间的数据);

迭代输出有序序列。

其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。

对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。

Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。

不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。

超过全国**%的用户

最早看到这个提示是在腾讯管家和360卫士上的开机助手提示,有点意思,那如何简单实现呢?

1、可以肯定的是我们不会去进行实时检索,而是采用离线计算。

2、我们可以基于正态分布,定期去计算一次均值与方差,然后通过标准化公式(X-μ)/σ转换成标准正太分布查表得出结果。

3a77eb65af39033eeda225c75bd489ae.png

3、或者更简单的方法是,直接用当前值除以最大值得出百分比。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值