Выполнение кода по расписанию с Whenever и Cron

Illustration of a person sitting on the floor leaning against a wall, engrossed in reading a tablet, highlighted by a beam of light on a dual-tone background. Illustration of a person sitting on the floor leaning against a wall, engrossed in reading a tablet, highlighted by a beam of light on a dual-tone background.

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