Un trabajo cron para rails: ¿mejores prácticas?


295

¿Cuál es la mejor manera de ejecutar tareas programadas en un entorno Rails? ¿Guión / corredor? ¿Rastrillo? Me gustaría ejecutar la tarea cada pocos minutos.


149
Para aquellos que vienen aquí de Google, mire más allá de la respuesta aceptada para obtener mejores enfoques.
jrdioko

44
La respuesta siempre parece más razonable que la respuesta aceptada, que es un viejo truco.
Rob

2
También tenga en cuenta que al menos una respuesta asume que tiene una cierta gema instalada.
Tass

Aquí se resumen un par de (lo que descubrí que son) buenas prácticas wisecashhq.com/blog/writing-reliable-cron-jobs
Thibaut Barrère

En muchos casos, los trabajos cron son un mal olor. Mejor escribir el planificador a través de sidekiq / resque (u otro trabajador en segundo plano), o escribir un demonio (menos funcional y monitoreable). Los trabajos de Cron tienen al menos algunas cosas malas: 1) bloquear para una instancia es una molestia; 2) el monitoreo no se puede hacer fácilmente; 3) el manejo de excepciones debe escribirse manualmente nuevamente; 4) no es fácil de reiniciar; 5) todos los problemas anteriores son fáciles de resolver por los trabajadores de fondo.
Dmitry Polushkin

Respuestas:


110

Estoy usando el enfoque de rastrillo (como lo admite heroku )

Con un archivo llamado lib / task / cron.rake ..

task :cron => :environment do
  puts "Pulling new requests..."
  EdiListener.process_new_messages
  puts "done."
end

Para ejecutar desde la línea de comandos, esto es solo "rake cron". Este comando se puede poner en el programador cron / task del sistema operativo como se desee.

¡Actualiza esta es una pregunta y respuesta bastante antigua! Alguna nueva información:

  • el servicio heroku cron al que hice referencia ha sido reemplazado por Heroku Scheduler
  • para tareas frecuentes (especialmente donde desea evitar el costo de inicio del entorno Rails), mi enfoque preferido es usar el cron del sistema para llamar a un script que (a) empuje una API webhook segura / privada para invocar la tarea requerida en segundo plano o (b) ponga en cola directamente una tarea en el sistema de colas que elija

¿Cuál debería ser la entrada cron para este caso, para que el sistema operativo conozca la ruta correcta a la tarea de rake?
jrdioko

13
NB: en estos días estoy usando siempre (vea la respuesta de Jim Garvin), pero una entrada cron sin procesar para ejecutar la tarea de rake sería algo así como: 30 4 * * * / bin / bash -l -c 'cd / opt / railsapp && RAILS_ENV = rake de producción cron --silent '
tardate

1
¿Cómo se llama esto desde la consola? Lo hice load "#{Rails.root}/lib/tasks/cron.rake"y rake cron, pero obtuve NameError: variable local indefinida o método `cron 'para main: Object
B Seven

3
El problema con este enfoque es la :environmentdependencia. Tenemos una aplicación Rails muy pesada que tarda mucho en iniciarse, nuestro Rake se llama cada minuto y consume más recursos al iniciar el entorno Rails que ejecutando la tarea . Me encantaría tener un entorno Rails ya iniciado para que se llame a través del cron, debe ser algo entre el enfoque del controlador y el entorno del rastrillo .
fguillen

¿Cuál es la duración de esta tarea? Estoy usando una condición if. Quiero saber con qué frecuencia se ejecuta esto. No puedo encontrar ninguna información sobre esto en el sitio web de heroku.
Shubham Chaudhary

254

He utilizado el extremadamente popular Whenever en proyectos que dependen en gran medida de tareas programadas, y es genial. Le brinda un buen DSL para definir sus tareas programadas en lugar de tener que lidiar con el formato crontab. Desde el archivo Léame:

Siempre es una gema de Ruby que proporciona una sintaxis clara para escribir y desplegar trabajos cron.

Ejemplo de README:

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

22
Si se ejecuta cada minuto, el entorno se reiniciará cada vez, lo que puede ser costoso. Parece que github.com/ssoroka/scheduler_daemon evita esto.
lulalala

3
+1 para mantener la configuración cron en su sistema de control de versiones
brittohalloran

3
Creo que esta es la mejor solución. Si está usando rieles, creo que es mejor escribir todo en rieles. Con este enfoque, también puede olvidarse de la tarea cron al cambiar de servidor, se mueve con la aplicación.
Adrian Matteo

Hay un gran Railscast sobre Whenever que es realmente útil (también está disponible una versión gratuita más antigua).
aceofbassgreg

@ Tony, Whenever es básicamente un lenguaje específico de dominio para escribir trabajos cron. Se compila en la sintaxis regular de cron en su servidor de rails y cron es lo que ejecuta los trabajos que especifique (generalmente a través de rails runner).
Greg

19

En nuestro proyecto, utilizamos por primera vez gema, pero enfrentamos algunos problemas.

Luego cambiamos a RUFUS SCHEDULER gema , que resultó ser muy fácil y confiable para programar tareas en Rails.

Lo hemos usado para enviar correos semanales y diarios, e incluso para ejecutar algunas tareas periódicas de rake o cualquier otro método.

El código utilizado en esto es como:

    require 'rufus-scheduler'

    scheduler = Rufus::Scheduler.new

    scheduler.in '10d' do
      # do something in 10 days
    end

    scheduler.at '2030/12/12 23:30:00' do
      # do something at a given point in time
    end

    scheduler.every '3h' do
      # do something every 3 hours
    end

    scheduler.cron '5 0 * * *' do
      # do something every day, five minutes after midnight
      # (see "man 5 crontab" in your terminal)
    end

Para obtener más información: https://github.com/jmettraux/rufus-scheduler


1
Para rufus, ya que lo he usado para proyectos ruby ​​simples o aplicaciones de rieles completos.
Paulo Fidalgo

8
¿Podrías ser un poco más específico sobre los problemas que encontraste con Whenever?
Duque

La respuesta más grandiosa
Darlan Dieterich

17

Suponiendo que sus tareas no tarden demasiado en completarse, simplemente cree un nuevo controlador con una acción para cada tarea. Implemente la lógica de la tarea como código del controlador, luego configure un cronjob en el nivel del sistema operativo que use wget para invocar la URL de este controlador y la acción en los intervalos de tiempo apropiados. Las ventajas de este método son usted:

  1. Tenga acceso completo a todos sus objetos Rails como en un controlador normal.
  2. Puede desarrollarse y probarse como lo hace con las acciones normales.
  3. También puede invocar sus tareas ad hoc desde una simple página web.
  4. No consuma más memoria activando procesos adicionales de ruby ​​/ rails.

12
¿Cómo evitar que otros accedan a esta tarea? Si la tarea de tomar CPU y llamarla con frecuencia causará problemas.
sarunw

44
Sé que esto fue hace un tiempo, pero definitivamente esta no es la mejor manera de hacer trabajos cron. ¿Por qué pasar por la interfaz web, violando lo que realmente representa la interfaz, cuando hay muchas otras formas de acceder al entorno Rails?
Matchu

66
La calificación "suponiendo que sus tareas no tarden demasiado en completarse" parece una ENORME. ¿No sería mejor utilizar un enfoque que sea más útil en general, y no solo en aquellos casos en que las tareas son muy rápidas? De esa manera, no reevalúa constantemente si esta o aquella tarea debe reescribirse utilizando un enfoque diferente.
iconoclasta

77
Esta vieja pregunta es el principal resultado de Google para "rails cron". Esta respuesta está lejos de ser el mejor enfoque. Consulte las otras respuestas para obtener sugerencias más sensatas.
Jim Garvin

2
No es la mejor manera. Tiene muchas otras formas de acceder a Rails env a través de un trabajo cron sin llamar a un servicio REST. El enfoque de ataque es ciertamente mejor
Shine

10

Las tareas de script / runner y rake están perfectamente bien para ejecutarse como trabajos cron.

Aquí hay una cosa muy importante que debe recordar al ejecutar trabajos cron. Probablemente no se llamarán desde el directorio raíz de su aplicación. Esto significa que todas sus necesidades de archivos (a diferencia de las bibliotecas) deben hacerse con la ruta explícita: por ejemplo, File.dirname (__ FILE__) + "/ other_file". Esto también significa que debe saber cómo llamarlos explícitamente desde otro directorio :-)

Compruebe si su código admite la ejecución desde otro directorio con

# from ~
/path/to/ruby /path/to/app/script/runner -e development "MyClass.class_method"
/path/to/ruby /path/to/rake -f /path/to/app/Rakefile rake:task RAILS_ENV=development

Además, los trabajos cron probablemente no se ejecuten como usted, por lo que no dependa de ningún acceso directo que ponga en .bashrc. Pero eso es solo un consejo cron estándar ;-)


Puede ejecutar el trabajo como cualquier usuario (solo configure la entrada crontab para el usuario que desee) pero tiene la razón de que el perfil y los scripts de inicio de sesión no se ejecutarán y no comenzará en su directorio de inicio. Entonces, es común comenzar el comando con un "cd" como se muestra en el comentario de @ luke-franci
Tom Wilson

10

El problema con when (y cron) es que vuelve a cargar el entorno de rails cada vez que se ejecuta, lo cual es un problema real cuando sus tareas son frecuentes o tienen mucho trabajo de inicialización. He tenido problemas en la producción debido a esto y debo advertirte.

El programador de Rufus lo hace por mí ( https://github.com/jmettraux/rufus-scheduler )

Cuando tengo trabajos largos para ejecutar, lo uso con delayed_job ( https://github.com/collectiveidea/delayed_job )

¡Espero que esto ayude!


10

Soy un gran admirador del programador resque / resque . No solo puede ejecutar tareas repetitivas similares a cron, sino también tareas en momentos específicos. La desventaja es que requiere un servidor Redis.


10

Eso es interesante, nadie mencionó el Sidetiq . Es una buena adición si ya estás usando Sidekiq.

Sidetiq proporciona una API simple para definir trabajadores recurrentes para Sidekiq.

Job se verá así:

class MyWorker
  include Sidekiq::Worker
  include Sidetiq::Schedulable

  recurrence { hourly.minute_of_hour(15, 45) }

  def perform
    # do stuff ...
  end
end

8

Ambos funcionarán bien. Usualmente uso script / runner.

Aquí hay un ejemplo:

0 6 * * * cd /var/www/apps/your_app/current; ./script/runner --environment production 'EmailSubscription.send_email_subscriptions' >> /var/www/apps/your_app/shared/log/send_email_subscriptions.log 2>&1

También puede escribir un script de Ruby puro para hacer esto si carga los archivos de configuración correctos para conectarse a su base de datos.

Una cosa a tener en cuenta si la memoria es preciosa es que el script / runner (o una tarea Rake que depende del 'entorno') cargará todo el entorno Rails. Si solo necesita insertar algunos registros en la base de datos, esto usará memoria que realmente no tiene que usar. Si escribe su propio guión, puede evitarlo. En realidad no he necesitado hacer esto todavía, pero lo estoy considerando.


8

Use Craken (trabajos de cron centrado en rastrillo)


1
escribir trabajos cron es muy difícil, mejor descarga una gema para eso
f0ster

1
no es difícil, pero tenerlos almacenados en git y siempre actualizados en la implementación es una gran ventaja cuando uno trabaja en un equipo.
Thibaut Barrère


3

Así es como configuré mis tareas cron. Tengo uno para hacer copias de seguridad diarias de la base de datos SQL (usando rake) y otro para caducar el caché una vez al mes. Cualquier salida se registra en un archivo log / cron_log. Mi crontab se ve así:

crontab -l # command to print all cron tasks
crontab -e # command to edit/add cron tasks

# Contents of crontab
0 1 * * * cd /home/lenart/izziv. whiskas.si/current; /bin/sh cron_tasks >> log/cron_log 2>&1
0 0 1 * * cd /home/lenart/izziv.whiskas.si/current; /usr/bin/env /usr/local/bin/ruby script/runner -e production lib/monthly_cron.rb >> log/cron_log 2>&1

La primera tarea cron realiza copias de seguridad diarias de db. Los contenidos de cron_tasks son los siguientes:

/usr/local/bin/rake db:backup RAILS_ENV=production; date; echo "END OF OUTPUT ----";

La segunda tarea se configuró más tarde y usa script / runner para expirar el caché una vez al mes (lib / Monthly_cron.rb):

#!/usr/local/bin/ruby
# Expire challenge cache
Challenge.force_expire_cache
puts "Expired cache for Challenges (Challenge.force_expire_cache) #{Time.now}"

Supongo que podría hacer una copia de seguridad de la base de datos de otra manera, pero hasta ahora funciona para mí :)

Los caminos al rastrillo y al rubí pueden variar en diferentes servidores. Puedes ver dónde están usando:

whereis ruby # -> ruby: /usr/local/bin/ruby
whereis rake # -> rake: /usr/local/bin/rake

3

Usar algo Sidekiq o Resque es una solución mucho más robusta. Ambos admiten el reintento de trabajos, la exclusividad con un bloqueo REDIS, el monitoreo y la programación.

Tenga en cuenta que Resque es un proyecto muerto (no mantenido activamente), por lo que Sidekiq es una alternativa mucho mejor. También es más eficiente: Sidekiq ejecuta a varios trabajadores en un solo proceso multiproceso, mientras que Resque ejecuta a cada trabajador en un proceso separado.


Esa es una respuesta correcta. Muchos pueden olvidarse de las buenas características que proporciona sidekiq o resque, como la interfaz web para monitorear lo que está sucediendo: número de trabajos en ejecución, fallidos o programados, reiniciarlos fácilmente, bloquear para trabajadores únicos, estrangulamiento y limitación, etc.
Dmitry Polushkin

3

Recientemente he creado algunos trabajos cron para los proyectos en los que he estado trabajando.

Encontré que la gema Clockwork es muy útil.

require 'clockwork'

module Clockwork
  every(10.seconds, 'frequent.job')
end

Incluso puedes programar tu trabajo en segundo plano con esta gema. Para obtener documentación y más ayuda, consulte https://github.com/Rykian/clockwork



2

Una vez tuve que tomar la misma decisión y estoy muy feliz con esa decisión hoy. Utilice el programador de resque porque no solo un redis separado eliminará la carga de su base de datos, sino que también tendrá acceso a muchos complementos como resque-web que proporciona una excelente interfaz de usuario. A medida que su sistema se desarrolle, tendrá que programar más y más tareas para poder controlarlas desde un solo lugar.


1

Probablemente la mejor manera de hacerlo es usar el rastrillo para escribir las tareas que necesita y simplemente ejecutarlo a través de la línea de comandos.

Puedes ver un video muy útil en railscasts

También eche un vistazo a estos otros recursos:


Intenté sin éxito utilizar la sintaxis en este tutorial. La tarea no se ejecutó.
Tass

1

Utilicé una gema mecánica y me funciona bastante bien. También hay una clockworkdgema que permite que un script se ejecute como demonio.


0

No estoy muy seguro, supongo que depende de la tarea: con qué frecuencia se ejecuta, qué tan complicado y cuánta comunicación directa se necesita con el proyecto de rieles, etc. Supongo que si hubiera "One Best Way" para hacer algo , no habría tantas formas diferentes de hacerlo.

En mi último trabajo en un proyecto de Rails, necesitábamos hacer un envío por correo de invitación por lotes (invitaciones a encuestas, no spam) que debería enviar los correos planificados siempre que el servidor tuviera tiempo. Creo que íbamos a usar herramientas de demonio para ejecutar las tareas de rastrillo que había creado.

Desafortunadamente, nuestra compañía tuvo algunos problemas de dinero y fue "comprada" por el principal rival, por lo que el proyecto nunca se completó, por lo que no sé qué podríamos haber utilizado.


0

Uso script para ejecutar cron, esa es la mejor manera de ejecutar un cron. Aquí hay un ejemplo para cron,

Abra CronTab -> sudo crontab -e

Y pegar líneas de fuelle:

00 00 * * * wget https: // your_host / some_API_end_point

Aquí hay un formato cron, te ayudará

::CRON FORMAT::

tabla de formato cron

Examples Of crontab Entries
15 6 2 1 * /home/melissa/backup.sh
Run the shell script /home/melissa/backup.sh on January 2 at 6:15 A.M.

15 06 02 Jan * /home/melissa/backup.sh
Same as the above entry. Zeroes can be added at the beginning of a number for legibility, without changing their value.

0 9-18 * * * /home/carl/hourly-archive.sh
Run /home/carl/hourly-archive.sh every hour, on the hour, from 9 A.M. through 6 P.M., every day.

0 9,18 * * Mon /home/wendy/script.sh
Run /home/wendy/script.sh every Monday, at 9 A.M. and 6 P.M.

30 22 * * Mon,Tue,Wed,Thu,Fri /usr/local/bin/backup
Run /usr/local/bin/backup at 10:30 P.M., every weekday. 

Espero que esto te ayudará :)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.