Un travail cron pour les rails: les meilleures pratiques?

295

Quelle est la meilleure façon d'exécuter des tâches planifiées dans un environnement Rails? Script / coureur? Râteau? Je voudrais exécuter la tâche toutes les quelques minutes.

jes5199
la source
149
Pour ceux qui viennent ici de Google, regardez au-delà de la réponse acceptée pour de meilleures approches.
jrdioko
4
La réponse à chaque fois semble plus raisonnable que la réponse acceptée, qui est un vieux hack.
Rob
2
Sachez également qu'au moins une réponse suppose que vous avez installé une certaine gemme.
Tass
Quelques bonnes pratiques (ce que j'ai découvert) sont résumées ici wisecashhq.com/blog/writing-reliable-cron-jobs
Thibaut Barrère
Dans de nombreux cas, les tâches cron sont une mauvaise odeur. Mieux écrire le planificateur via sidekiq / resque (ou tout autre travailleur en arrière-plan), ou écrire un démon (moins fonctionnel et contrôlable). Les tâches Cron ont au moins quelques mauvaises choses: 1) le verrouillage pour une seule instance est une douleur; 2) le suivi ne peut pas se faire facilement; 3) la gestion des exceptions doit être à nouveau écrite manuellement; 4) pas facile à redémarrer; 5) tous les problèmes ci-dessus sont facilement résolus par les travailleurs de fond
Dmitry Polushkin

Réponses:

110

J'utilise l'approche de râteau (telle que prise en charge par Heroku )

Avec un fichier appelé lib / tasks / cron.rake ..

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

Pour exécuter à partir de la ligne de commande, il s'agit simplement de "rake cron". Cette commande peut ensuite être placée sur le planificateur cron / tâche du système d'exploitation comme vous le souhaitez.

Mettre à jour c'est une question et une réponse assez anciennes! Quelques nouvelles infos:

  • le service cron heroku auquel j'ai fait référence a depuis été remplacé par Heroku Scheduler
  • pour les tâches fréquentes (en particulier lorsque vous voulez éviter les coûts de démarrage de l'environnement Rails), mon approche préférée consiste à utiliser cron système pour appeler un script qui (a) déclenchera une API de webhook sécurisé / privé pour appeler la tâche requise en arrière-plan ou (b) mettre directement en file d'attente une tâche sur le système de file d'attente de votre choix
tardive
la source
Quelle devrait être l'entrée cron dans ce cas, afin que le système d'exploitation connaisse le chemin d'accès correct à la tâche de râteau?
jrdioko
13
NB: ces jours-ci, j'utilise à chaque fois (voir la réponse de Jim Garvin), mais une entrée cron brute pour exécuter la tâche de râteau serait quelque chose comme: 30 4 * * * / bin / bash -l -c 'cd / opt / railsapp && RAILS_ENV = production râteau cron
silencieux
1
Comment appelez-vous cela depuis la console? J'ai fait load "#{Rails.root}/lib/tasks/cron.rake"et rake cron, mais j'ai obtenu NameError: variable locale non définie ou méthode `cron 'pour main: Object
B Seven
3
Le problème avec cette approche est la :environmentdépendance. Nous avons une application Rails très lourde qui prend du temps à démarrer, notre Rake est appelé toutes les minutes et consomme plus de ressources au démarrage de l' environnement Rails qui exécute la tâche . J'aimerais avoir un environnement Rails déjà démarré pour être appelé via le cron, être quelque chose entre l' approche du contrôleur et celle de l' environnement rake .
fguillen
Quelle est la durée de cette tâche? J'utilise une condition if. Je veux savoir à quelle fréquence cela fonctionne. Je ne trouve aucune information à ce sujet sur le site Web Heroku.
Shubham Chaudhary
254

J'ai utilisé le très populaire Whenever sur des projets qui reposent fortement sur des tâches planifiées, et c'est génial. Il vous donne une belle DSL pour définir vos tâches planifiées au lieu d'avoir à gérer le format crontab. Du README:

Chaque fois qu'il s'agit d'un joyau Ruby qui fournit une syntaxe claire pour l'écriture et le déploiement de tâches cron.

Exemple du 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
Jim Garvin
la source
22
S'il est exécuté toutes les minutes, l'environnement sera redémarré à chaque fois, ce qui peut être coûteux. Il semble que github.com/ssoroka/scheduler_daemon évite cela.
lulalala
3
+1 pour conserver la configuration cron avec votre système de contrôle de version
brittohalloran
3
Je pense que c'est la meilleure solution. Si vous utilisez des rails, je pense qu'il vaut mieux tout écrire dans les rails. Avec cette approche, vous pouvez également oublier la tâche cron lors du changement de serveur, elle se déplace avec l'application.
Adrian Matteo
Il y a un excellent Railscast sur Whenever qui est vraiment utile (une ancienne version gratuite est également en place).
aceofbassgreg
@Tony, Whenever est fondamentalement un langage spécifique au domaine pour écrire des tâches cron. Il se compile en syntaxe cron régulière sur votre serveur rails et cron exécute les tâches que vous spécifiez (généralement via rails runner).
Greg
19

Dans notre projet, nous avons d'abord utilisé chaque fois que gem, mais nous avons rencontré des problèmes.

Nous sommes ensuite passés à RUFUS SCHEDULER gem, qui s'est avéré très facile et fiable pour la planification des tâches dans Rails.

Nous l'avons utilisé pour envoyer des courriers hebdomadaires et quotidiens, et même pour exécuter des tâches de râteau périodiques ou toute autre méthode.

Le code utilisé ici est comme:

    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

Pour en savoir plus: https://github.com/jmettraux/rufus-scheduler

Pankhuri
la source
1
Pour rufus, comme je l'ai utilisé à la fois pour de simples projets rubis ou des applications de rails complets.
Paulo Fidalgo du
8
Pourriez-vous être un peu plus précis sur les problèmes que vous avez rencontrés avec Whenever?
Duke
la meilleure réponse de tous les temps
Darlan Dieterich
17

En supposant que vos tâches ne prennent pas trop de temps à terminer, créez simplement un nouveau contrôleur avec une action pour chaque tâche. Implémentez la logique de la tâche en tant que code de contrôleur, puis configurez un cronjob au niveau du système d'exploitation qui utilise wget pour appeler l'URL de ce contrôleur et agir aux intervalles de temps appropriés. Les avantages de cette méthode sont les suivants:

  1. Ayez un accès complet à tous vos objets Rails comme dans un contrôleur normal.
  2. Peut se développer et tester tout comme vous faites des actions normales.
  3. Peut également invoquer vos tâches adhoc à partir d'une simple page Web.
  4. Ne consommez plus de mémoire en activant des processus rubis / rails supplémentaires.
Freakent
la source
12
Comment empêcher les autres d'accéder à cette tâche? Si la tâche de prendre cpu et de l'appeler fréquemment entraînera des problèmes.
sarunw
44
Je sais que c'était il y a quelque temps, mais ce n'est certainement plus la meilleure façon de faire des tâches cron. Pourquoi passer par l'interface Web, violant ce que l'interface représente vraiment, alors qu'il existe de nombreuses autres façons d'accéder à l'environnement Rails?
Matchu
6
La qualification "en supposant que vos tâches ne prennent pas trop de temps à terminer" semble énorme. Ne serait-il pas préférable d'utiliser une approche plus généralement utile, et pas seulement dans les cas où les tâches sont très rapides? De cette façon, vous ne réévaluez pas constamment si telle ou telle tâche doit être réécrite en utilisant une approche différente.
iconoclaste du
77
Cette vieille question est le meilleur résultat google pour "rails cron". Cette réponse est loin d'être la meilleure approche. Veuillez consulter les autres réponses pour des suggestions plus sensées.
Jim Garvin
2
Pas le meilleur moyen. Vous avez de nombreuses autres façons d'accéder à Rails env via une tâche cron sans appeler un service REST. L'approche par râteau est certainement meilleure
Shine
10

les tâches script / runner et rake sont parfaitement adaptées à l'exécution en tant que tâches cron.

Voici une chose très importante à retenir lors de l'exécution de tâches cron. Ils ne seront probablement pas appelés depuis le répertoire racine de votre application. Cela signifie que tout ce dont vous avez besoin pour les fichiers (par opposition aux bibliothèques) doit être fait avec le chemin explicite: par exemple File.dirname (__ FILE__) + "/ other_file". Cela signifie également que vous devez savoir comment les appeler explicitement à partir d'un autre répertoire :-)

Vérifiez si votre code prend en charge l'exécution à partir d'un autre répertoire avec

# 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

De plus, les tâches cron ne s'exécutent probablement pas comme vous, donc ne dépendez pas des raccourcis que vous mettez dans .bashrc. Mais ce n'est qu'une astuce cron standard ;-)

webmat
la source
Vous pouvez exécuter le travail comme n'importe quel utilisateur (il suffit de définir l'entrée crontab pour l'utilisateur que vous voulez), mais vous avez raison de dire que le profil et les scripts de connexion ne s'exécuteront pas et que vous ne démarrerez pas dans votre répertoire personnel. Il est donc courant de lancer la commande avec un "cd" comme indiqué dans le commentaire de @ luke-franci
Tom Wilson
10

Le problème à chaque fois (et cron) est qu'il recharge l'environnement des rails à chaque exécution, ce qui est un vrai problème lorsque vos tâches sont fréquentes ou ont beaucoup de travail d'initialisation à faire. J'ai eu des problèmes de production à cause de cela et je dois vous avertir.

L'ordonnanceur Rufus le fait pour moi ( https://github.com/jmettraux/rufus-scheduler )

Lorsque j'ai de longs travaux à exécuter, je l'utilise avec delay_job ( https://github.com/collectiveidea/delayed_job )

J'espère que ça aide!

Abdo
la source
10

Je suis un grand fan de resque / resque scheduler . Vous pouvez non seulement exécuter des tâches répétitives de type cron, mais également des tâches à des moments spécifiques. L'inconvénient est qu'il nécessite un serveur Redis.

Tyler Morgan
la source
10

C'est intéressant, personne n'a mentionné le Sidetiq . C'est un ajout intéressant si vous utilisez déjà Sidekiq.

Sidetiq fournit une API simple pour définir des travailleurs récurrents pour Sidekiq.

Le travail ressemblera à ceci:

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

  recurrence { hourly.minute_of_hour(15, 45) }

  def perform
    # do stuff ...
  end
end
Alexander Paramonov
la source
8

Les deux fonctionneront bien. J'utilise généralement un script / runner.

Voici un exemple:

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

Vous pouvez également écrire un script Ruby pur pour ce faire si vous chargez les bons fichiers de configuration pour vous connecter à votre base de données.

Une chose à garder à l'esprit si la mémoire est précieuse est que le script / runner (ou une tâche Rake qui dépend de «l'environnement») chargera tout l'environnement Rails. Si vous avez seulement besoin d'insérer des enregistrements dans la base de données, cela utilisera de la mémoire dont vous n'avez pas vraiment besoin. Si vous écrivez votre propre script, vous pouvez éviter cela. Je n'ai pas encore eu besoin de le faire, mais j'y réfléchis.

Luke Francl
la source
8

Utiliser Craken (travaux cron centrés sur le râteau)

Thibaut Barrère
la source
1
écrire des tâches cron est si difficile, mieux vaut télécharger un bijou pour ça
f0ster
1
ce n'est pas difficile - mais les avoir stockés dans git et toujours à jour lors du déploiement est un gros plus quand on travaille en équipe.
Thibaut Barrère
5

J'utilise backgroundrb.

http://backgroundrb.rubyforge.org/

Je l'utilise pour exécuter des tâches planifiées ainsi que des tâches qui prennent trop de temps pour la relation client / serveur normale.

salt.racer
la source
3

Voici comment j'ai configuré mes tâches cron. J'en ai un pour faire des sauvegardes quotidiennes de la base de données SQL (en utilisant le râteau) et un autre pour expirer le cache une fois par mois. Toute sortie est enregistrée dans un fichier journal / cron_log. Mon crontab ressemble à ceci:

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 première tâche cron effectue des sauvegardes quotidiennes de la base de données. Le contenu de cron_tasks est le suivant:

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

La deuxième tâche a été configurée plus tard et utilise un script / runner pour expirer le cache une fois par mois (lib / mensuel_cron.rb):

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

Je suppose que je pourrais sauvegarder la base de données d'une autre manière, mais jusqu'à présent, cela fonctionne pour moi :)

Les chemins d' accès au râteau et au rubis peuvent varier sur différents serveurs. Vous pouvez voir où ils se trouvent en utilisant:

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

la source
3

Utiliser quelque chose de Sidekiq ou Resque est une solution beaucoup plus robuste. Ils prennent tous deux en charge les nouvelles tentatives, l'exclusivité avec un verrou REDIS, la surveillance et la planification.

Gardez à l'esprit que Resque est un projet mort (non activement maintenu), Sidekiq est donc une bien meilleure alternative. Il est également plus performant: Sidekiq exécute plusieurs travailleurs sur un seul processus multithread tandis que Resque exécute chaque travailleur dans un processus distinct.

jaysqrd
la source
Voilà une bonne réponse. Beaucoup peuvent oublier les fonctionnalités intéressantes fournies par sidekiq ou resque, telles que l'interface Web pour surveiller ce qui se passe: nombre de travaux en cours, échoués ou planifiés, les redémarrer facilement, verrouiller pour des travailleurs uniques, limiter et limiter, etc.
Dmitry Polushkin
3

J'ai récemment créé des emplois cron pour les projets sur lesquels je travaille.

J'ai trouvé que le joyau Clockwork était très utile.

require 'clockwork'

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

Vous pouvez même planifier votre travail d'arrière-plan en utilisant ce joyau. Pour la documentation et l'aide supplémentaire, consultez https://github.com/Rykian/clockwork

Vipul Lawande
la source
2

Une fois, j'ai dû prendre la même décision et je suis vraiment content de cette décision aujourd'hui. Utilisez l' ordonnanceur resque car non seulement un redis séparé supprimera la charge de votre base de données, vous aurez également accès à de nombreux plugins comme resque-web qui fournit une excellente interface utilisateur. Au fur et à mesure que votre système se développe, vous aurez de plus en plus de tâches à planifier afin que vous puissiez les contrôler à partir d'un seul endroit.

Caner Çakmak
la source
1

La meilleure façon de le faire est probablement d'utiliser rake pour écrire les tâches dont vous avez besoin et de simplement l'exécuter via la ligne de commande.

Vous pouvez voir un très utile vidéo sur railscasts

Jetez également un œil à ces autres ressources:

Adrià Cidre
la source
J'ai essayé en vain d'utiliser la syntaxe de ce tutoriel. La tâche n'a pas été exécutée.
Tass
1

J'ai utilisé un bijou d' horlogerie et cela fonctionne assez bien pour moi. Il existe également une clockworkdgemme qui permet à un script de s'exécuter en tant que démon.

nnattawat
la source
0

Je ne suis pas vraiment sûr, je suppose que cela dépend de la tâche: à quelle fréquence exécuter, combien compliqué et combien de communication directe avec le projet de rails est nécessaire, etc. Je suppose que s'il y avait juste "One Best Way" pour faire quelque chose , il n'y aurait pas tant de façons différentes de le faire.

Lors de mon dernier emploi dans un projet Rails, nous devions créer un mailing d'invitation par lots (invitations à des sondages, pas de spam) qui devrait envoyer les mails planifiés chaque fois que le serveur en avait le temps. Je pense que nous allions utiliser des outils démon pour exécuter les tâches de râteau que j'avais créées.

Malheureusement, notre entreprise a eu des problèmes d'argent et a été "achetée" par le principal rival, donc le projet n'a jamais été achevé, donc je ne sais pas ce que nous aurions finalement utilisé.

Stein G. Strindhaug
la source
0

J'utilise un script pour exécuter cron, c'est la meilleure façon d'exécuter un cron. Voici un exemple pour cron,

Ouvrir CronTab -> sudo crontab -e

Et collez les lignes ci-dessous:

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

Voici un certain format cron, vous aidera

::CRON FORMAT::

table au format 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. 

J'espère que ceci vous aidera :)

Suis-je
la source