Выполнение кода по расписанию с Whenever и Cron
Нередко при написании веб-приложения программист хочет воплотить некую функцию, которая должна будет выполняться снова и снова по определенному расписанию. Практически всегда в дело идет cron, планировщик задач в UNIX-системах. Cron позволяет задавать выполнение необходимых задач (таких, как запуск программ, создание или удаление файлов, очистка логов и прочее) через необходимый промежуток времени, начиная с минимального промежутка – раз в минуту – и заканчивая выполнением задачи лишь раз в год.
Создание расписаний для cron обычно делается простым редактированием файла конфигурации, который вызывается командой crontab -e
. Однако, создавать расписания таким образом в контексте разрабатываемого веб-приложения на Ruby on Rails неразумно, нелогично и попросту неудобно. Здесь на сцену выходит гем whenever, позволяющий создавать расписание естественным кодом на Ruby, который так же легко читать и писать.
Стоит заметить, что whenever – не более, чем надстройка над cron, благодаря которой мы можем писать расписания, используя красивый DSL. В момент выполнения код гема переводит наши расписания в вид, понятный cron и обновляет его файл расписаний.
Для начала, по традиции добавим в Gemfile строку:
gem 'whenever', require: false
Обратите внимание на параметр require: false
, он указан для того, чтобы код гема не выполнялся при запуске приложения. В этом нет необходимости, поскольку whenever отвечает только за обновление расписания задач, которое обычно происходит при деплое приложения.
Установим гем командой bundle install
. В корневой папке приложения, где будет использоваться whenever, инициализируем файл расписания командой wheneverize .
. Результатом выполнения команды будет создание файла config/schedule.rb
, который, как видно из названия, и будет содержать в себе расписание задач, которые необходимо будет выполнять в контексте веб-приложения.
Рассмотрим пример содержимого файла schedule.rb
:
every 3.hours do
runner "MyModel.some_process"
rake "my:rake:task"
command "/usr/bin/my_great_command"
end
every 1.day, at: '4:30 am' do
runner "MyModel.task_to_run_at_four_thirty_in_the_morning"
end
every :hour do # Many shortcuts available: :hour, :day, :month, :year, :reboot
runner "SomeModel.ladeeda"
end
every :sunday, at: '12pm' do # Use any day of the week or :weekend, :weekday
runner "Task.clear_logs"
end
every '0 0 27-31 * *' do
command "echo 'you can use raw cron syntax too'"
end
# run this task only on servers with the :app role in Capistrano
every :day, at: '12:20am', roles: [:app] do
rake "app_server:task"
end
Как видно, задавать время, через которое будет выполняться задача, можно несколько способами:
- в стиле Ruby:
every 3.days
- указывая конкретное время:
every 1.day, at: '4:30 am'
- с определенной переодичностью:
every :hour/day/week/month/year
- указывая день недели:
every :sunday
- используя синтаксис cron:
every '0 0 27-31 * *'
При необходимости гем позволяет создать собственный тип задачи:
job_type :awesome, '/usr/local/bin/awesome :task :fun_level'
every 2.hours do
awesome "party", fun_level: "extreme"
end
Создание собственного типа задачи может быть полезно, если необходимо написать более или менее громоздкий сценарий и нет желания хранить его весь внутри исполняемого блока задачи.
whenever предоставляет максимальную гибкость при создании расписания для выполнения задач: условия и время, как описано в примере выше, можно комбинировать между собой для достижения максимально эффективного распорядка. Гем поддерживает интеграцию с Capistrano, что может быть удобно при деплое приложения на несколько серверов.
Для интеграции расписаний whenever в процесс деплоя при помощи Capistrano необходимо проделать несколько дополнительных шагов. При использовании Capistrano версии 3 и выше необходимо добавить в файл Capfile строку:
require "whenever/capistrano"
Так же в файл config/deploy.rb
добавим строку:
set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" }
Строка выше устанавливает параметры для неймспейса в файле крона в зависимости от названия и рабочей среды приложения.
Использование whenever (а значит и любых задач, выполняемых по расписанию) может быть удобно во множестве случаев. Например, для сервиса, имеющего платную подписку, разумно будет раз в сутки проверять, не истек ли у кого-нибудь из пользователей срок подписки:
every 1.day, at: 00:00 do
rake "subscription:check"
end
Еще одним подходящим примером использования задач, выполняемых по расписанию, будет агрегация контента. Предположим, после закрытия Google Reader нам понадобилась собственная читалка RSS-лент. Для такого приложения вполне типичной будет следующая задача:
every 1.hour
rake "rss:fetch"
end
Таким образом, использование cron и whenever открывает множество возможностей для ваших приложений.