2013-02-19 3 views
10

Benutzer abonnieren E-Mails, die die letzten Videos enthalten, aber sie legen auch fest, wann diese E-Mails abgerufen werden sollen.Wie Sie wiederkehrende Aufgaben mit der vom Benutzer eingestellten Zeit/Frequenz korrekt implementieren

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 

Wie können wir von den Benutzern in ihrer Zeitzone der E-Mails an der genauen Zeit und Frequenz gewählt senden, ohne vermasseln (wie doppelte E-Mails zu senden oder die Zeit fehlt)

Die einzigen Frequenzen sind täglich und wöchentlich, wenn täglich, dann ist der Tag null.

Ich benutze redis als Datenbank dafür, lassen Sie mich wissen, wie man das richtig macht!

+0

wenn Sie delayed_job verwendet haben - https://github.com/collectiveidea/delayed_job - Sie run_at auf jeden Job zu etwas einstellen Sie möchten (passen Sie Ihre Subskriptionszeit an, ein Job pro Zeitplan), und wenn der Job abgeschlossen ist, reiht es sich in die Warteschlange ein, basierend auf Ihrer Scheduler-Einstellung – house9

+0

Wie genau müssen Sie sein? Wenn es von einer Minute ist, ist das akzeptabel? –

Antwort

0

Ich glaube, Sie dieses Juwel für diesen Zweck nutzen können: https://github.com/bvandenbos/resque-scheduler

Grüße,

+0

Nicht sicher, ob es die richtige Lösung ist. Scheint, dass wiederkehrende Aufgaben nicht dynamisch erstellt werden können (ohne Verwendung einer YAML-Konfigurationsdatei). – Ryan

+0

@Ryan In der Taskkonfiguration heißt es ansonsten: # Wenn Sie den Zeitplan dynamisch ändern können , # kommentieren Sie diese Zeile. Ein dynamischer Zeitplan kann über die Methoden # Resque :: Scheduler.set_schedule (und remove_schedule) aktualisiert werden. # Wenn dynamic auf true gesetzt ist, sucht der Scheduler-Prozess nach # schedule changes und wendet sie im laufenden Betrieb an. # Hinweis: Diese Funktion ist nur in> = 2.0.0 verfügbar. #Resque :: Scheduler.dynamic = true – fmendez

0

Sie die DelayedJob oder Resque nutzen könnten Gems, die jede Instanz darstellen einer geplanten Aufgabe als eine Zeile in einer Datenbanktabelle. Es gibt Methoden zum Auslösen, Planen und Abmelden von Aufgaben aus dem Kalender.

1

Ich habe delayed_job für ähnliche Aufgaben in der Vergangenheit verwendet. Wahrscheinlich können Sie die gleiche Technik mit resque verwenden. Im Wesentlichen müssen Sie den nächsten Job am Ende des aktuellen Jobs planen.

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 

Neuterminierung Einsprüche

Aktualisieren Sie den Code am Zyklusende zu behandeln. Sie können dies umgehen, indem Sie eine boolean Spalte mit der Bezeichnung active hinzufügen (standardmäßig auf true einstellen). Um das Abonnement zu deaktivieren, legen Sie die Spalte auf false fest.

def send_email 
    return unless active? 
    # do stuff and then schedule the next run 
    ensure 
    send_email if active? 
    end 

die max_attempts für den Job 1. Andernfalls setzen Sie die Warteschlange überschwemmen. In der obigen Lösung werden die Jobs für send_email einmal versucht.

+0

Die Antwort wurde aktualisiert, um 'max_attempts' elegant zu handhaben. –

2

Ich werde auf die Antwort von fmendez mit der resque-scheduler gem erweitern.

Lassen Sie uns zuerst die Arbeiter schaffen, die die E-Mails

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 

In Ihrem Abonnement-Modell sendet, fügen Sie ein next_sending_time Methode das nächste Mal berechnen eine E-Mail gesendet werden soll.

# 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 
0

ich das für mein Projekt nur umgesetzt habe, habe ich eine wirklich einfache Möglichkeit gefunden, es zu tun ist zu verwenden, wenn Gem (hier https://github.com/javan/whenever gefunden).

Um loszulegen, um Ihre Anwendung gehen und

gem 'whenever' 

dann verwenden setzen:

wheneverize . 

im Terminal.Dadurch wird eine Datei "schedule.rb" in Ihrem Konfigurationsordner erstellt.

Sie setzen Ihre Regeln in Ihre schedule.rb (siehe unten) und lassen sie bestimmte Methoden aufrufen - zum Beispiel ruft meins die Modellmethode DataProviderUser.cron auf, die den Code ausführt, den ich dort habe.

Sobald Sie diese Datei erstellt haben, den Cron-Job zu starten, über die Verwendung Befehlszeile:

whenever 
whenever -w 

und

whenever -c 

Stops/löscht die Cron-Jobs.

Die Dokumentation zu Gemsen auf Github ist wirklich nützlich, aber ich empfehle Ihnen, die Ausgabe auf Ihre eigene Protokolldatei (wie ich unten getan habe). Hoffe, das hilft :)

in meinem schedule.rb ich habe:

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 
1

Dies ist eher ein Systemebene Problem als spezifisch für Rubin.

Zunächst einmal speichern Sie alle Ihre Zeit intern als GMT. Zeitzonen sind nicht real, abgesehen davon, dass sie eine Voreinstellung (in der Tabelle des Benutzers) sind, die die Zeit in der Ansicht ausgleicht. Das Koordinatensystem, in dem die Mathematik ausgeführt wird, sollte konsistent sein.

Dann entspricht jede Frequenz einer Cronjob-Einstellung: täglich, Montag, Dienstag, etc. Wirklich, aus den Daten in der Tabelle, brauchen Sie nicht zwei Spalten, es sei denn, Sie sehen dies als Änderung.

Dann, wenn der Cron ausgelöst wird, verwenden Sie einen Scheduler (wie Linux AT) zu behandeln, wenn die E-Mail ausgeht. Dies ist eher ein Scheduling-Problem auf Systemebene, oder zumindest würde ich dem System eher trauen, dies zu handhaben. Es muss den Fall behandeln, in dem das System neu gestartet wird, einschließlich der Dienste und der App.

Ein Nachteil ist jedoch, dass wenn Sie eine große Menge an E-Mails senden, Sie wirklich nicht garantieren können, dass die E-Mail nach Wunsch gesendet wird. Sie müssen es vielleicht zurückdrosseln, um zu vermeiden, dass es gelöscht/blockiert wird (um 1000/Nachrichten über eine Stunde verteilt zu sagen). Unterschiedliche Netzwerke haben unterschiedliche Nutzungsschwellen.

Also im Grunde:

cron -> an -> E-Mail senden

Verwandte Themen