3

我们感兴趣的是记录和计算一个项目在搜索或列表页面中出现的次数。每天有 50,000 个独立访客,我们预计每天可以产生 3-4 百万“印象”,这不是一个非常高的数量,但我们希望很好地构建一个。

我们不需要实时读取这些数据,但希望能够生成每日总计和分析趋势等。类似于业务分析工具。

我们计划在页面呈现后使用 Ajax 帖子执行此操作 - 这将允许我们对结果进行计数,即使这些结果已被缓存。我们可以在每页的单个帖子中执行此操作,以发送以逗号分隔的 id 列表及其在页面上的位置。

我希望有一些关于此的设计模式/宝石/博客文章可以帮助我避免可能出现的常见的初学者错误。我也没有太多记录或阅读日志的经验。

我目前的策略 - 制作一些东西来将事件写入日志文件,并在一天结束时统计结果并将结果放回 mysql 中。

4

3 回答 3

3

好的,我为您提供了三种方法:

1) 队列

在您的 AJAX 处理程序中,编写可能的最简单方法(使用 Rack 中间件或 Rails Metal)将查询参数推送到队列。然后,轮询队列并收集消息。

来自机架中间件的队列推送速度非常快。我们在流量非常大的网站上使用它来记录类似数据。

下面是一个示例机架中间件(从我们的应用程序中提取,可以在 <2ms 左右的时间内处理请求:

class TrackingMiddleware
  CACHE_BUSTER = {"Cache-Control" => "no-cache, no-store, max-age=0, must-revalidate", "Pragma" => "no-cache", "Expires" => "Fri, 29 Aug 1997 02:14:00 EST"}

  IMAGE_RESPONSE_HEADERS = CACHE_BUSTER.merge("Content-Type" => "image/gif").freeze
  IMAGE_RESPONSE_BODY = [File.open(Rails.root + "public/images/tracker.gif").read].freeze

  def initialize(app)
    @app = app
  end

  def call(env)
    if env["PATH_INFO"] =~ %r{^/track.gif}
      request = Rack::Request.new(env)
      YOUR_QUEUE.push([Time.now, request.GET.symbolize_keys])
      [200, IMAGE_RESPONSE_BODY, IMAGE_RESPONSE_HEADERS]
    else
      @app.call(env)
    end
  end
end

对于我推荐Starling的队列,我度过了愉快的时光。

在解析端,我会使用super-poller 工具包,但我会说,我写的。

2) 日志

将所有参数作为查询参数传递到静态文件 (/1x1.gif?foo=1&bar=2&baz=3)。这不会撞到rails stack并且会非常快。

当您需要数据时,只需解析日志文件!

这是最好的缩放家庭酿造方法。

3) 谷歌分析

当谷歌会为你做这件事时,为什么要处理负载?您会惊讶于谷歌分析的出色程度,在您在家酿造任何东西之前,请先检查一下!

这将无限扩展,因为谷歌购买服务器的速度比你快。


我可以为此咆哮很久,但我现在必须走了。希望这可以帮助!

于 2009-10-20T17:43:15.617 回答
1

根据列出项目所需的操作,您可能可以在控制器中执行此操作并为自己节省往返行程。您可以使用 after_filter 来完成,以使添加不显眼。

这仅在列出您要记录的项目的所有操作都需要参数时才有效。这是因为页面缓存会忽略带有参数的 GET 请求。

假设您只想在搜索操作中记录搜索数据。

class ItemsController < ApplicationController
  after_filter :log_searches, :only => :search

  def log_searches
    @items.each do |item|
      # write to log here
    end
  end

  ...
  # rest of controller remains unchanged
  ...
end

否则,您就可以使用 AJAX 和 onload 远程功能。

至于处理,您可以使用由 cron 作业运行的 rake 任务来收集统计信息,并可能更新项目以获得受欢迎程度。

无论哪种方式,您都需要阅读Ruby Logging 类。了解 cron 作业和 rake 任务也不会受到伤害。

于 2009-10-20T17:36:44.637 回答
1

这就是我最终所做的——这对于我们现在的使用来说已经足够了,并且通过一些简单的基准测试,我觉得还可以。在我们将结果展示给客户之前,我们将观察它在生产中的表现。

组件:

class EventsController < ApplicationController
  def create
    logger = Logger.new("#{RAILS_ROOT}/log/impressions/#{Date.today}.log")
    logger.info "#{DateTime.now.strftime} #{params[:ids]}" unless params[:ids].blank?
    render :nothing => true
  end
end

这是从站点布局中的 ajax 调用调用的...

<% javascript_tag do %>
  var list = '';
  $$('div.item').each(function(item) { list += item.id + ','; });
  <%= remote_function(:url => { :controller => :events, :action => :create}, :with => "'ids=' + list" ) %>
<% end %>

然后我做了一个 rake 任务,将这些逗号分隔的 id 行导入到数据库中。第二天运行:

desc "Calculate impressions"
task :count_impressions => :environment do
  date = ENV['DATE'] || (Date.today - 1).to_s # defaults to yesterday (yyyy-mm-dd)
  file = File.new("log/impressions/#{date}.log", "r")
  item_impressions = {}
  while (line = file.gets)
    ids_string = line.split(' ')[1]
    next unless ids_string
    ids = ids_string.split(',')
    ids.each {|i| item_impressions[i] ||= 0; item_impressions[i] += 1 }
  end
  item_impressions.keys.each do |id|
    ActiveRecord::Base.connection.execute "insert into item_stats(item_id, impression_count, collected_on) values('#{id}',#{item_impressions[id]},'#{date}')", 'Insert Item Stats'
  end

  file.close
end

需要注意的一件事 - 记录器变量是在控制器操作中声明的 - 而不是在 environment.rb 中,就像您通常对记录器所做的那样。我对此进行了基准测试 - 10000 次写入大约需要 20 秒。平均一次写入大约需要 2 毫秒。使用 envirnment.rb 中的文件名,大约需要 14 秒。我们进行了这种权衡,以便我们可以动态确定文件名 - 一种在午夜切换文件的简单方法。

在这一点上,我们主要关心的是——我们不知道每天将计算多少不同的项目——即。我们不知道尾巴有多长。这将确定每天向数据库添加多少行。我们预计我们将需要限制我们保留每日报告的时间,并且届时将进一步提升结果。

于 2009-10-22T20:45:30.610 回答