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

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

让我们说下面的模型:

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

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

目前我有一个无限的rake任务运行,它会永久检查电子邮件是否可用于发送,我想把每日总和生成并发送到这个rake任务。

我有一个想法,我可以使用以下伪代码解决这个问题:

 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任务,这个工作非常好。

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

后台流程/工人/ ……

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

  1. 直接从数据库检查条件 ,它比加载潜在的无用数据更有效
  2. 批量加载用户 。 想象一下,你的数据库包含数百万用户…我很确定你会很高兴,但不是Rails ……根本不是。 🙂

在你的代码旁边,我看到了另一个问题。 您将如何在生产服务器上管理此后台作业? 如果您不想使用Resque或其他东西,您应该考虑以另一种方式管理它。 MonitGod都是一个过程监视器。

 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任务有一个很好的gem。 这里有更多的信息: 每当

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

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

 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任务。

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

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

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

 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建议的查询将比您拥有的查询效率更高。

如果你不想使用队列 – 考虑延迟作业(一种糟糕的勒芒队列) – 它确实作为一个rake任务运行,类似于你正在做的

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

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

你是如何更新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属性越来越“延迟”。