如何正确实现用户设置的时间/频率的重复任务

时间:2013-02-19 18:18:08

标签: ruby-on-rails ruby redis whenever

用户订阅包含最后视频的电子邮件,但他们也会设置何时收到这些电子邮件。

Subscription(user_id, frequency, day, time, time_zone)

user_id  |  frequency  |  day    |  time   |  time_zone
1        |  daily      |  null   |  16:00  |  GMT
2        |  weekly     |  friday |  11:00  |  UTC
3        |  weekly     |  monday |  18:00  |  EST

我们如何以用户在其时区选择的确切时间和频率发送电子邮件,而不会搞砸(例如发送双重电子邮件或丢失时间)

唯一的频率是每日和每周,如果是每天,则该日为空。

我使用redis作为数据库,让我知道如何以正确的方式做到这一点!

6 个答案:

答案 0 :(得分:2)

我将使用resque-scheduler gem扩展fmendez的答案。

首先,让我们创建发送电子邮件的工作人员

class SubscriptionWorker
  def self.perform(subscription_id)
    subscription = Subscription.find subscription_id

    # ....
    # handle sending emails here
    # ....

    # Make sure that you don't have duplicate workers
    Resque.remove_delayed(SubscriptionWorker, subscription_id)        

    # this actually calls this same worker but sets it up to work on the next
    # sending time of the subscription.  next_sending_time is a method that
    # we implement in the subscription model.
    Resque.enqueue_at(subscription.next_sending_time, SubscriptionWorker, subscription_id)
  end
end

在订阅模型中,添加next_sending_time方法以计算下次发送电子邮件的时间。

# subscription.rb
def next_sending_time
  parsed_time = Time.parse("#{time} #{time_zone}") + 1.day

  if frequency == 'daily'
    parsed_time
  else
    # this adds a day until the date matches the day in the subscription
    while parsed_time.strftime("%A").downcase != day.downcase
      parsed_time += 1.day
    end
  end
end

答案 1 :(得分:1)

我过去曾使用delayed_job执行类似的任务。可能你可以使用与resque相同的技术。基本上,您必须在当前作业结束时安排下一个作业。

class Subscription < ActiveRecord::Base
  after_create :send_email        
  def send_email 
    # do stuff and then schedule the next run
  ensure
    send_email
  end
  handle_asynchronously :send_email, :run_at => Proc.new{|s| s.deliver_at }

  def daily? (frequency == "daily");end
  def max_attempts 1;end

  def time_sec
    hour,min=time.split(":").map(&:to_i)
    hour.hours + min.minutes
  end

  def days_sec
    day.nil? ? 0 : Time::DAYS_INTO_WEEK[day.to_sym].days
  end

  def interval_start_time
    time = Time.now.in_time_zone(time_zone)
    daily? ?  time.beginning_of_day : time.beginning_of_week
  end

  def deliver_at
    run_at = interval_start_time + days_sec + time_sec
    if time.past?
      run_at = daily? ? run_at.tomorrow : 1.week.from_now(run_at)
    end
    run_at
  end        
end

重新安排警告

更新代码以处理循环终止。您可以通过添加名为boolean的{​​{1}}列来处理此问题(默认情况下将其设置为active)。要禁用订阅,请将列设置为false。

true

将作业的 def send_email return unless active? # do stuff and then schedule the next run ensure send_email if active? end 设置为1.否则,您将填充队列。在上面的解决方案中,max_attempts的作业将尝试一次。

答案 2 :(得分:1)

这更像是一个系统级问题,而不是特定于ruby。

首先,将所有时间都存储在GMT内部。时区不是真实的,除了作为偏好设置(在用户的表中)偏移视图中的时间。完成数学运算的坐标系应该是一致的。

然后每个频率对应一个cronjob设置:每天,星期一,星期二等。真的,从表格中的数据来看,你不需要两列,除非你认为这是改变。

然后,当cron触发时,使用调度程序(如linux AT)来处理电子邮件何时消失。这更多的是系统级调度问题,或者至少,我更信任系统来处理这个问题。它需要处理重启系统的情况,包括服务和应用程序。

但有一点需要注意的是,如果您要发送大量邮件,实际上无法保证邮件会根据喜好发送。您可能实际上必须将其限制回来以避免被转储/阻止(例如,1000 /消息传播超过一小时)。不同的网络将具有不同的使用阈值。

基本上是这样的:

cron - &gt; at - &gt;发送邮件

答案 3 :(得分:0)

我认为你可以为此目的利用这个宝石:https://github.com/bvandenbos/resque-scheduler

此致

答案 4 :(得分:0)

您可以使用 DelayedJob Resque Gems,它们将计划任务的每个实例表示为数据库表中的一行。有一些方法可以从日历中触发,安排和撤销任务。

答案 5 :(得分:0)

我刚刚为我的项目实现了这个,我发现一个非常简单的方法就是使用Whenever Gem(在这里找到https://github.com/javan/whenever)。

要开始使用,请转到您的应用并输入

gem 'whenever'

然后使用:

wheneverize .

在终端。这将在配置文件夹中创建schedule.rb文件。

您将规则放在schedule.rb中(如下所示)并让它们调用某些方法 - 例如,我调用模型方法DataProviderUser.cron,它将运行我在那里的任何代码。

创建此文件后,要启动cron作业,请在命令行中使用:

whenever
whenever -w

whenever -c

停止/清除cron作业。

github上的gems文档非常有用,但我建议你将输出设置为你自己的日志文件(正如我在下面所做的那样)。希望有所帮助:)

在我的schedule.rb中我有:

set :output, 'log/cron.log'
every :hour do
runner "DataProviderUser.cron('hourly')"
end

every :day do
runner "DataProviderUser.cron('daily')"
end

every '0 0 * * 0' do
runner "DataProviderUser.cron('weekly')"
end

every 14.days do
runner "DataProviderUser.cron('fortnightly')"
end

every :month do
runner "DataProviderUser.cron('monthly')"
end