Rails 3.1 / rake - задачи для конкретных дат без очередей - PullRequest
1 голос
/ 03 января 2012

Я хочу, чтобы мои пользователи могли отправлять им ежедневную сводку статистики своего аккаунта в определенное (заданное пользователем) время ....

Допустим, следующая модель:

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

В настоящее время существует ли лучшая практика отправки этой учетной записи по электронной почте в указанное время?

В данный момент у меня запущено бесконечное рейк-задание, которое постоянно проверяет, доступны ли электронные письма для отправки, и я бы хотел включить генерацию dailysummary-генерации и отправку в эту грабли.

У меня была мысль, что я могу решить эту проблему следующим псевдокодом:

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 для управления этой задачей, которая отлично работает.

Ответы [ 6 ]

2 голосов
/ 09 января 2012

Периодическое выполнение задачи - это то, для чего нужен cron. Каждый раз, когда gem (https://github.com/javan/whenever)) упрощает настройку определений cron для вашего приложения.

По мере масштабирования вашего приложения вы можете обнаружить, что задача rake выполняется слишком долго и что очередь полезна поверх планирования cron. Вы можете использовать cron для контроля, когда поставки запланированы, но фактически выполнять их рабочим пулом.

2 голосов
/ 08 января 2012

Есть только два основных способа сделать отложенное выполнение.Вы запускаете скрипт, когда пользователь на вашем сайте попадает на страницу, что неэффективно и не совсем точно.Или используйте какой-нибудь фоновый процесс, будь то задание cron или resque / отложенное задание и т. Д.

Хотя ваш метод запуска процесса rake навсегда будет работать нормально, он неэффективен, потому что вы перебираете пользователей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, хотя он запускает вашу задачу по рейку.

1 голос
/ 14 января 2012

Я вижу две возможности выполнить задачу в определенное время.

Фоновый процесс / Рабочий / ...

Это то, что вы уже сделали.Я реорганизовал ваш пример, потому что было две плохих вещи.

  1. Проверка условий непосредственно из вашей базы данных , это более эффективно, чем загрузка потенциально бесполезных данных
  2. Загрузка пользователей партиями .Представьте, что ваша база данных содержит миллионы пользователей ... Я уверен, что вы были бы счастливы, но не Rails ... совсем нет.:)

Кроме вашего кода я вижу еще одну проблему.Как вы собираетесь управлять этой фоновой работой на вашем производственном сервере?Если вы не хотите использовать Resque или что-то еще, вам следует подумать об управлении этим другим способом.Существует Monit и Бог , которые являются мониторами процесса.

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?Я думаю, что пусть они выберут час, достаточно .

Применяя это, я думаю, что работа, выполняемая каждый час, лучше , чем бесконечная задача, запрашивающая вашу базу данных каждыйминута.Более того, это действительно легко сделать , потому что вам не нужно ничего настраивать.

Существует хороший гем для управления задачей cron с синтаксисом ruby.Больше информации здесь: Всякий раз

0 голосов
/ 14 января 2012

Как вы обновили атрибут 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 все более и более «задержанным».

0 голосов
/ 11 января 2012

Если вы не хотите использовать очередь - рассмотрите отложенную работу (вроде очереди плохого человека) - она ​​запускается как задание с граблями, похожее на то, что вы делаете

хранит все задачи в таблице заданий, обычно, когда вы добавляете задачу, ставит ее в очередь для запуска как можно скорее, однако вы можете переопределить это, чтобы отложить ее до определенного времени

вы можете преобразовать свой класс DailySummary в DailySummaryJob, а после его завершения он может поставить в очередь новый экземпляр самого себя для следующих дней выполнения

0 голосов
/ 08 января 2012

Вы можете сделать это, вам также нужно будет проверить время, когда вы хотите отправить. Итак, начнем с вашего псевдокода и добавим к нему:

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, чтобы вам не пришлось без необходимости забивать вашу базу данных. Вы также можете захотеть ограничить этот цикл только набором пользователей, которым вы хотите отправить. Запрос, подобный тому, что предлагает Захари, будет гораздо более эффективным, чем у вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...