Rails 3.1 / rake - 没有队列的特定日期任务

时间:2012-01-02 21:28:27

标签: ruby-on-rails ruby ruby-on-rails-3 rake scheduled-tasks

我想让我的用户可以选择在特定的(用户指定的)时间向他们发送他们的帐户统计信息的每日摘要....

让我们说下面的模型:

class DailySummery << ActiveRecord::Base
  # attributes:
  # send_at
  # => 10:00 (hour)
  # last_sent_at
  # => Time of the last sent summary
end

现在是否有最佳做法如何通过电子邮件将此帐户摘要发送到特定时间?

目前我正在运行一个无限的佣金任务,如果电子邮件可以发送,我会永久检查,我想把每日总计生成并发送到这个佣金任务。

我想我可以用以下伪代码来解决这个问题:

while true
  User.all.each do |u|
    u.generate_and_deliver_dailysummery if u.last_sent_at < Time.now - 24.hours
  end
  sleep 60
end

但我不确定这是否有一些隐藏的警告......

注意:我不想使用像resq或redis这样的队列等等!

编辑:添加了睡眠(已在我的脚本中使用)

编辑:这是一项时间关键服务(交易费率通知)所以它应该尽可能快。这就是为什么我不想使用队列或基于作业的系统的背景。我使用Monit来管理这个rake任务,这个工作非常好。

6 个答案:

答案 0 :(得分:2)

只有两种主要方法可以延迟执行。当您站点上的用户访问页面时,您运行该脚本,这是低效且不完全准确的。或者使用某种后台进程,无论是cron作业还是resque /延迟作业等等。

虽然你的rake过程永远运行的方法可以正常工作,但效率很低,因为你一旦完成就会全天候迭代用户,例如:

while true
    User.where("last_sent_at <= ? OR last_sent_at = ?", 24.hours.ago, nil).each do |u|
            u.generate_and_deliver_dailysummery
    end

    sleep 3600
end

每小时运行一次,只需拉动需要发送电子邮件的用户就会更有效率。最好的做法是使用cronjob虽然可以运行你的rake任务。

答案 1 :(得分:2)

定期运行任务是cron的用途。随时可以使用gem(https://github.com/javan/whenever),为您的应用程序配置cron定义变得简单。

随着您的应用扩展,您可能会发现rake任务运行时间太长,并且队列在cron调度之上非常有用。您可以使用cron来控制何时安排交付,但实际上是由工作池执行它们。

答案 2 :(得分:1)

我看到在特定时间执行任务的两种可能性。

后台流程/工作人员/ ...

这就是你已经做过的事情。我重构了你的例子,因为有两件坏事。

  1. 直接从您的数据库检查条件,它比加载潜在的无用数据更有效
  2. 按批次加载用户。想象一下,你的数据库包含数百万用户...我很确定你会很开心,但不是Rails ......根本不是。 :)
  3. 除了你的代码,我还看到了另一个问题。您将如何在生产服务器上管理此后台作业?如果您不想使用Resque或其他东西,您应该考虑以另一种方式管理它。 Monit God 都是流程监控器。

    while true
      # Check the condition from your database
      users = User.where(['last_sent_at < ? OR created_at IS NULL', 24.hours.ago])
      # Load by batch of 1000
      users.find_each(:batch_size => 1000) do |u|
         u.generate_and_deliver_dailysummery
      end
      sleep 60
    end
    

    Cron职位/预定任务/ ......

    第二种可能性是递归地安排您的任务,例如每小时或半小时。如果我错了,请纠正我,但是你的用户真的需要在上午10:39安排发货吗?我认为让他们选择小时就够了

    应用这个,我认为每小时发出的作业比每分钟查询数据库的无限任务更好。此外非常容易,因为您不需要设置任何内容。

    使用ruby语法管理cron任务有一个很好的宝石。更多信息:Whenever

答案 3 :(得分:0)

你可以这样做,你还需要检查你想要发送的时间。所以从你的伪代码开始并添加它:

while true
  User.all.each do |u|
    if u.last_sent_at < Time.now - 24.hours && Time.now.hour >= u.send_at
      u.generate_and_deliver_dailysummery
      # the next 2 lines are only needed if "generate_and_deliver_dailysummery" doesn't sent last_sent_at already
      u.last_sent_at = Time.now  
      u.save
    end
  end

  sleep 900
end

我还添加了sleep,因此您不必毫不费力地锤击您的数据库。您可能还希望将该循环限制为仅需要发送到的用户集。类似Zachary建议的查询将比您拥有的查询效率更高。

答案 4 :(得分:0)

如果你不想使用队列 - 考虑延迟作业(一种糟糕的勒芒队列) - 它确实像你正在做的那样执行rake任务

它将所有任务存储在作业表中,通​​常在您添加任务时将其排队以尽快运行,但是您可以覆盖此任务以将其延迟到特定时间

您可以将DailySummary类转换为DailySummaryJob,一旦完成,它可以为下一天的运行重新排队自己的新实例

答案 5 :(得分:0)

您是如何更新last_sent_at属性的?

如果您使用

last_sent_at += 24.hours  

并使用last_sent_at = Time.now.at_beginning_of_day + send_at

初始化

一切都会好的。

不要使用last_sent_at = Time.now。这是因为当工作实际完成时可能会有一些延迟,这会使last_sent_at属性越来越“延迟”。