繁体   English   中英

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

[英]Rails 3.1/rake - datespecific tasks without queues

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

可以说以下模型:

class DailySummery << ActiveRecord::Base
  # attributes:
  # send_at
  # => 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任务,该任务确实很好。

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

尽管使rake进程永久运行的方法可以很好地工作,但它效率不高,因为您要在24/7上对用户进行一次24/7的迭代,例如:

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来控制何时安排交货,但实际上由工作人员池执行交货。

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

后台进程/工作者/ ...

这是您已经完成的。 我重构了您的示例,因为有两个不好的地方。

  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任务。 更多信息在这里: 每当

您可以执行此操作,还需要检查发送时间。 因此,从您的伪代码开始并添加到其中:

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

如果您不想使用队列,请考虑延迟的工作(有点像个穷人队列),它确实像您正在执行的耙任务一样运行

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

您可以将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属性越来越“延迟”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM