实现排行榜

4

用户在我的网站上为说唱歌词(示例)创建注释。我想创建一个排行榜,以奖励创建最多注释的人。

排行榜应跟踪每个用户总共创建了多少注释,以及他在过去一周、一天等时间段内创建了多少注释。

我没有问题实现总体排行榜:

@users = User.all

<table>
  <tr>
    <th>Contributor</th>
    <th>Annotations</th>
  </tr>
    <% @users.sort_by{|u| u.annotations.size }.reverse.each do |u| %>
      <tr>
        <td><%= u %></td>
        <td><%= u.annotations.size %></td>
      </tr>
    <% end %>
</table>

但是当我尝试实现(比如)每日得分板时,我会重复代码,操作非常缓慢(因为它必须遍历内存中的每个注释,而不能依赖数据库排序/计数):

<table>
  <tr>
    <th>Contributor</th>
    <th>Annotations</th>
  </tr>
    <% @users.sort_by{|u| u.annotations.select{|a| a.created_at > 1.day.ago }.size }.reverse.each do |u| %>
      <tr>
        <td><%= u %></td>
        <td><%= u.annotations.select{|a| a.created_at > 1.day.ago }.size %></td>
      </tr>
    <% end %>
</table>

如何最好地实现每日/每周的记分板?

4个回答

12

整体而言,排行榜的实现是一件痛苦的事情。在我的经验中,实际的实现相当直观,只是难以扩展。通常你会发现自己不得不运行许多数据库查询,这些查询对数据库来说相当密集。为了处理每日/每周报告,你可能会查询一个日期时间列,但这意味着你必须在该列上创建一个索引。该索引只对排行榜查询有用,它使得该表的所有其他写操作都需要付出代价,因为索引必须重新计算。

另一种方法是按预定间隔生成统计数据,并将该数据写入一个单独的表中,供排行榜查询使用。例如,您可以设置一个后台作业,每晚运行一个查询(可能是昂贵的,因为它不使用日期时间索引,但由于它仅运行一次并通过后台作业运行,因此这个费用是可以接受的)。查询反过来写入一个带有日期时间列索引的统计表,然后重写排行榜页面以命中预先计算的统计数据。根据您的需求,您可能还希望让cron脚本进行其他数据转换和预计算,以便排行榜页面尽可能少地进行计算。

此时,您的排行榜页面已经工作,虽然它命中了一个带有索引的表,但仍然需要读取大量的行。这是假设您有相当不错的流量情况下。在每个页面上让带有索引的查询命中大量行仍然很昂贵。因此,现在你可以考虑实现页面缓存,例如在memcached中存储数据。也就是说,由于每日排行榜数据每天至少更改一次,根据定义,在每个页面视图上重新运行这些数据库查询是昂贵的。将每日数据缓存在memcached中,每个页面视图只命中memcached会更加合理。

因此,您可以看到这是一个逐步发展的过程。如果您的流量较低,那么您可能可以不用单独的表格,只需在datetime列上建立索引。运行总和、计数和平均值可能还可以。但它不可扩展。那么您就必须考虑将其拆分为更优化的结构。然后您会发现,每天都在运行相同的查询,而基础数据在24小时内并没有改变,这是昂贵的,因此您需要转向缓存设置。其包含很多组成部分,可能会变得复杂,实际上只是变得很快乏味。

当涉及排行榜时,我是一个经验丰富的愤世嫉俗者,虽然它们对于游戏机制和激励人们(每个人都喜欢看到分数!)非常好,但在大规模使用时让它起作用是一件麻烦事。


3

您是否考虑将这些统计数据保存在一个单独的表/模型中,由观察者更新?您在视图中进行了很多重复操作,这通常不是一个好的做法。


3
我建议使用Redis。您可以运行类似于cron的任务,从您的数据库中获取数据,然后将其放入Redis排序集中。排序集特性可能是存储排行榜最好的实用工具。 http://redis.io/topics/data-types

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接