海量用户积分排名算法分析

问题:

某海量用户网站,用户拥有积分,积分可能会在使用过程中随时更新。现在要为该网站设计一种算法,在每次用户登录时

显示其当前积分排名。用户最大规模为2亿;积分为非负整数,且小于100万。

存储结构

首先,我们用一张用户积分表user_score来保存用户的积分信息。

表结构:

FieldTypeNullKeyDefault
uid
score
int(11)
int(11)
NO
NO
PRI0
0

示例数据:

                     

uidscore
1232
2100289
30
499
5878
699999
7298892
869891
98
10555

下面的算法会基于这个基本的表结构来进行。

算法1:简单的SQL查询

首先,我们很容易想到用一条简单的SQL语句查询出积分大于该用户积分的用户数量:

 select 1+count(t2.uid)as rank from user_score t1,user_score t2 where t1.uid=@uid and t2.score>t1.score

算法特点

优点:简单,利用了SQL的功能,不需要复杂的查询逻辑,也不引入额外的存储结构,对小规模或性能要求不高的应用不失

为一种良好的解决方案。缺点:需要对user_score表进行全表扫描,还需要考虑到查询的同时若有积分更新会对表造成锁定,

在海量数据规模和高并发的应用中,这样做性能是无法接受的。  

算法2:均匀分区设计

在许多应用中缓存是解决性能问题的重要途径,我们自然会想:能不能把用户排名用Memcached缓存下来呢?不过再一想 

发现缓存似乎帮不上什么忙,因为用户排名是一个全局性的统计指标,而并非用户的私有属性,其他用户的积分变化可能会

马上影响到本用户的排名。然而,真实的应用中积分的变化其实也是有一定规律的,通常一个用户的积分不会突然暴增暴减,

一般用户总是要在低分区混迹很长一段时间才会慢慢升入高分区,也就是说用户积分的分布总体说来是有区段的,我们进一

步注意到高分区用户积分的细微变化其实对低分段用户的排名影响不大。于是,我们可以想到按积分区段进行统计的方法,

引入一张分区积分表score_range:

表结构:

                    

FieldTypeNullKeyDefault
from_scoreint(11)NOPRI0
to_scoreint(11)NOPRI0
countint(11)NO 0


数据示例:

from_scoreto_scorecount
0100048892
1000200025329
2000300012568
3000400010207

表示[from_score,to_score)区间有count个用户。若我们按照每1000分划分一个区间则有[0,1000),[1000,2000),...,[999000,1000
000)这1000个区间,以后对用户积分的更新要相应的更新score_range表的区间值。在分区积分表的辅助下查询积分为s的用户
的排名,可以首先确定其所属区间,把高于s的积分区间的count值累加,然后再查询出该用户在本区间内的排名,二者相加即可
获得用户的排名。
这个方法貌似通过区间聚合减少了查询计算量,最大的问题在于:如何查询用户在本区间内的排名呢?如果是在算法1中的SQL
中加上积分条件:
        select 1+count(t2.uid)as rank from user_score t1, user_score t2 where t1.uid = @uid and t2.score > t1.score 
        and t2.score < @to_score
在理想情况下,由于把 t2.score的范围限制在了1000以内,如果对score字段建立索引,我们期望本条SQL语句将通过索引大大
减少扫描的user_score表的行数。不过真实情况并非如此,t2.score的范围在1000以内并不意味着该区间内的用户数也是1000,
因为这里有积分相同的情况存在!二八定律告诉我们,前20%的低分区往往集中了80%的用户,这就是说对于大量低分区用户进
行区间内排名查询的性能远不及对少数高分区用户进行排名查询,所以在一般情况下这种分区方法不会带来实质性的性能提升。
算法特点
优点:注意到了积分区间的存在,并通过预先聚合消除查询的全表扫描。
缺点:积分非均匀分布的特点使得性能提升并不理想。

算法3:积分排名数组
仔细观察一下积分变化对排名的具体影响,可以发现 某用户的积分从s变为s+n,积分小于s或者大于等于s+n的其他用户排名实
际上并不会受到影响,只有积分在[s,s+n)区间内的用户排名会下降1位。我们可以用一个大小为100 000 000的数组表示积分和 
排名的对应关系,其中rank[s]表示积分s所对应的排名。初始化时,rank数组可以由user_score表在O(n)的复杂度内计算而来。
用户排名的查询和更新基于这个数组来进行。查询积分s所对应的排名直接返回rank[s]即可,复杂度为O(1);当用户积分从s变为
s+n,只需要把rank[s]到rank[s+n-1]这n个元素的值增加1即可,复杂度为O(n)。
算法特点
优点:积分排名数组查询复杂度为O(1);排名更新复杂度O(n),在积分变化不大的情况下非常高效。
缺点:当n比较大时,需要更新大量元素。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值