海量用户的积分排序问题算法的分析

标签: 算法 rank | 发表时间:2016-01-07 21:22 | 作者:niceforbear
出处:http://segmentfault.com/blogs

Question

某海量用户网站,用户拥有积分,积分可能会在使用过程中随时更新。现在要为该网站设计一种算法,在每次用户登录时显示其当前积分排名。用户最大规模为2亿;积分为非负整数,且小于100万。

表(user_score)的结构可以如下设计:

Field Type Null Key Default
uid int(11) NO PRI 0
score int(11) NO 0

Algorithm 1

Thinking

通过使用一个简单的SQL语句查询出积分大于该用户积分的用户数量:

  SELECT 1+ COUNT(t2.uid) AS rank FROM user_score t1, user_score t2 WHERE t1.uid = @uid AND t2.score > t1.score; 

缺点:需要对user_score进行全表扫描,还要考虑到查询的同时若有积分的更新会产生死锁。海量数据规模以及高并发的应用中不适用。

Algorithm 2 均匀分区设计

Thinking

  1. 用户排名是一个全局性的统计指标,而非用户的私有属性,缓存在这里并不适用。

  2. 具体的进行问题分析:真实的用户积分变化是有一定规律的,通常用户积分都不会暴增暴减。一般用户都是在低分区,即用户积分的分布总体来说是有区段的。同时,高分区用户的细微变化对低分段用户排名影响不大。

  3. 考虑按积分区段进行统计方法,引入分区积分表 score_range.

Field Type Null Key Default
from_score int(11) NO PRI 0
to_score int(11) NO PRI 0
count int(11) NO 0

该表表示,在积分区间 [from_score, to_score) 有count个用户

对用户积分的更新要相应地更新该表的区间值。在 score_range的辅助下,查询积分为s的用户的排名,通过累加高积分区间的 count值,再计算用户在本区间内的排名即可获得结果。

该方法貌似通过区间聚合减少了查询计算量。不过有问题是:如果查询用户在本区间内的排名呢?

  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; 

如果对 score字段建立索引,我们期望该SQL语句将通过索引大大减少扫描的 user_score表的行数。

不过根据二八定律,对于大量低分区用户进行区间内排名查询的性能不及对少数高分区用户进行查询。对于一般用户来讲,并没有实质性的性能提升。

优点:通过建立积分区间,减少全表扫描。
缺点:积分分布的不均匀导致性能并不理想。

Algorithm 3 树形分区设计

Thinking

再次考虑,是否可以按照二八定律,把 score_range表设计为非均匀区间,把低分区划分密集一点。Eg:开始设置10分一个区间,然后区间逐渐变成100分,1000分……

不过该方法随机性较大,同时系统的积分分布会随着使用而逐渐变化。我们希望找到一种分区方法,既可以适应积分非均匀性,又可以适应系统积分分布的变化。这就是树形分区。

我们把[0, 1m)作为一级区间,再二分为两个2级区间[0, 500k), [500k, 1m),以此类推,最终获得21级区间[0,1), ... , [999999,1m)。

实际上把区间组织为了平衡二叉树结构。树形分区结构需要在更新时保持一种不变量:
非叶子结点的count值 == 左右子节点的count之和

每次用户积分有变化所需要更新的区间数量和积分变化量有关系,积分变化越小更新的区间层次越低。每次需要更新的区间数量是用户积分变量的 log n级别。

在该积分表的辅助下查询积分为s的用户排名,实际上市在一个区间树上由上至下明确s所在位置的过程。s积分的排名即是排在他前面的区间的 count的累加。

本算法的更新和查询都设计若干个操作,但如果为区间 from_scoreto_score建立索引,这些操作都是基于键的查询和更新,不会产生全表扫描。

同时该算法并不依赖关系数据模型和SQL运算,可以轻易地改造为NoSQL。而基于键的操作也很容易引入缓存机制进一步优化性能。

估算一下,树形区间的数目大约为 2billion个,考虑每个节点的大小,整个结构只需要 几十m空间。可以在内存建立区间树结构,,通过 user_score表在 O(n)时间内初始化区间树。

优点:
不受积分分布影响;
每次查询或更新的复杂度为积分最大值的 O(log n)级别,且与用户规模无关;
不依赖SQL,容易改造为内存数据结构

Algorithm 4 积分排名数组

Thinking

Algo3的时间复杂度只在n特别大的时候才具有优势,而实际应用中积分的变化情况往往不大,这时和 O(n)算法相比没有明显优势。

仔细观察积分变化对于排名的影响,可以发现某用户的积分从s变为s+n,只有积分在 [s, s+n)区间的用户排名会下降1名。我们可以用一个大小为1M的数组表示积分和排名的对应关系, rank[s]表示积分s所对应的排名。初始化时,rank数组可以由 user_score表在 O(n)的复杂度内计算而来。查询积分s所对应的排名直接返回 rank[s]即可,当用户积分从s变为s+n,只需要把 rank[s]rank[s+n-1]这n个元素的值加1即可,复杂度为 O(n)

参考

  1. 某年某月的《码农》期刊

相关 [用户 积分 排序] 推荐:

海量用户的积分排序问题算法的分析

- - SegmentFault 最新的文章
某海量用户网站,用户拥有积分,积分可能会在使用过程中随时更新. 现在要为该网站设计一种算法,在每次用户登录时显示其当前积分排名. 用户最大规模为2亿;积分为非负整数,且小于100万. 表(user_score)的结构可以如下设计:. 通过使用一个简单的SQL语句查询出积分大于该用户积分的用户数量:.

用户积分功能的设计

- - 四火的唠叨
文章系本人原创,转载请保持完整性并注明出自 《四火的唠叨》. 有一个SNS应用,用户在使用的过程中积累积分,例如登陆+3点,个人空间每次浏览+1点,结交每个朋友+5点等等. 同时,很重要的一点是,用户需要看到自己的积分累计有多少,能够根据积分划分用户等级,在自己的空间展示积分. 在用户量比较大的情况下(例如超过三千万),这是一个比较典型的读写都很频繁的问题,而且写入的次数可能和读取的次数差别不大(大多数SNS应用中,读次数远超写次数的场景居多,例如用户的状态信息,更新一次以后有成千上万的访问).

堆排序

- kongshanzhanglao - 博客园-首页原创精华区
       堆排序是利用堆的性质进行的一种选择排序.   堆实际上是一棵完全二叉树,其任何一非叶节点满足性质:.   Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2].   即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关键字.

排序算法

- - 互联网 - ITeye博客
排序算法有很多,所以在特定情景中使用哪一种算法很重要. 为了选择合适的算法,可以按照建议的顺序考虑以下标准: .     对于数据量较小的情形,(1)(2)差别不大,主要考虑(3);而对于数据量大的,(1)为首要.  一、冒泡(Bubble)排序——相邻交换 .  二、选择排序——每次最小/大排在相应的位置 .

lucene排序

- - 开源软件 - ITeye博客
排序是对于全文检索来言是一个必不可少的功能,在实际运用中,排序功能能在某些时候给我们带来很大的方便,比如在淘宝,京东等一些电商网站我们可能通过排序来快速找到价格最便宜的商品,或者通过排序来找到评论数最高或卖的最好的商品,再比如在Iteye里的博客栏里,每天都会以降序的方式,来显示出最新发出的几篇博客,有了排序,我们就能在某些时候很方便快速的得到某些有效信息,所以说排序功能,无处不在 ^_^.

巧解定积分:用定积分来求解定积分

- Elims - Matrix67: My Blog
    今天在这里学到一种很诡异的定积分方法. 看看下面这个定积分,你打算怎么求解.     当然,我们假设 a 、 b 满足这个定积分的值确实存在.     下面是一个很妙的做法:.     正所谓“用定积分来求解定积分”,实在让人眼前一亮. 这告诉我们,“化简”并不一定要化“简”,把问题还原为更复杂的形式,反而会有出其不意的效果.

【原】MapReduce的排序和二次排序

- - ITeye博客
自己学习排序和二次排序的知识整理如下. 1.Hadoop的序列化格式介绍:Writable. 2.Hadoop的key排序逻辑. 4.如何自定义自己的Writable类型. 1.Hadoop的序列化格式介绍:Writable. 要了解和编写MR实现排序必须要知道的第一个知识点就是Writable相关的接口和类,这些是HADOOP自己的序列化格式.

Java排序算法:归并排序

- - zzm
 Java排序算法(九):归并排序. 归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的. 然后再把有序子序列合并为整体有序序列. 归 并排序是建立在归并操作上的一种有效的排序算法. 该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.

排序大比武

- niko - C++博客-首页原创精华区
     摘要: 该比武只比速度,单线程测试随机正整数,不包括蜗牛排序,它弃权啦,哈哈. 操作系统:Windows XP Pro SP3,英文版编译器:g++4.5.2(-O3)CPU: Intel Core2 Q9500内存:DDR3普条 1066MHz, 4GB 插入排序、选择排序和冒泡排序最慢,时间复杂度为O(n2),希尔排序的速度依赖于使用的增量序列,堆排序、归并排序和改进的快速排序处于中等水平,时间复杂度...  阅读全文.

hadoop 二次排序

- - 企业架构 - ITeye博客
hadoop的工作流程:. 是在key中,排序value的实现,思路是. 1.把value中需要有序的部分value-part放入key中. 2.sortCompare类或key的CompareTo方法中完成对key+value-part的比较. 3.GroupingCompare中只对key进行比较,这样相同的key跌倒获取到reduce中.