Um trabalho cron para rails: melhores práticas?


295

Qual é a melhor maneira de executar tarefas agendadas em um ambiente Rails? Script / corredor? Ancinho? Eu gostaria de executar a tarefa a cada poucos minutos.


149
Para quem vem do Google, veja além da resposta aceita para obter melhores abordagens.
Jrdioko

4
A resposta sempre parece mais razoável do que a resposta aceita, que é um truque antigo.
Rob

2
Lembre-se também de que pelo menos uma resposta pressupõe que você tenha uma determinada jóia instalada.
Tass

Um par de (o que eu descobri ser) as boas práticas são resumidas aqui wisecashhq.com/blog/writing-reliable-cron-jobs
Thibaut Barrère

Em muitos casos, os trabalhos cron são um mau cheiro. Melhor agendador de gravação através do sidekiq / resque (ou outro trabalhador em segundo plano) ou escreva um daemon (menos funcional e monitorável). Os empregos temporários têm pelo menos algumas coisas ruins: 1) bloquear uma instância é uma dor; 2) o monitoramento não pode ser feito facilmente; 3) o tratamento de exceções deve ser escrito manualmente novamente; 4) não é fácil reiniciar; 5) todas as questões acima resolvidas facilmente pelos trabalhadores em segundo plano.
Dmitry Polushkin

Respostas:


110

Estou usando a abordagem rake (conforme suportado por heroku )

Com um arquivo chamado lib / tasks / cron.rake ..

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

Para executar a partir da linha de comando, isso é apenas "rake cron". Esse comando pode ser colocado no cron / agendador de tarefas do sistema operacional, conforme desejado.

Atualizar esta é uma pergunta e resposta bastante antigas! Algumas novas informações:

  • o serviço heroku cron que referenciei foi substituído pelo Heroku Scheduler
  • para tarefas frequentes (especialmente onde você deseja evitar o custo de inicialização do ambiente Rails), minha abordagem preferida é usar o cron do sistema para chamar um script que (a) cutuca uma API webhook segura / privada para invocar a tarefa necessária em segundo plano ou (b) enfileirar diretamente uma tarefa no seu sistema de filas de escolha

Qual deve ser a entrada cron neste caso, para que o sistema operacional conheça o caminho correto para a tarefa rake?
Jrdioko

13
NB: hoje em dia estou usando sempre (consulte a resposta de Jim Garvin), mas uma entrada cron bruta para executar tarefas de rake seria algo como: 30 4 * * * / bin / bash -l -c 'cd / opt / railsapp && RAILS_ENV = rake de produção cron --silent '
atrasado 12/07

1
Como você chama isso no console? Eu fiz load "#{Rails.root}/lib/tasks/cron.rake"e rake cron, mas obtive NameError: variável local indefinida ou método `cron 'para main: Object
B Seven

3
O problema com essa abordagem é a :environmentdependência. Temos um aplicativo Rails muito pesado que demora muito para iniciar, nosso Rake é chamado a cada minuto e consome mais recursos iniciando o ambiente Rails que executa a tarefa . Eu adoraria ter um ambiente Rails já iniciado para ser chamado através do cron, deve haver algo entre a abordagem do controlador e a do ambiente rake .
fguillen

Qual é a duração desta tarefa? Estou usando uma condição if. Quero saber com que regularidade isso é executado. Não consigo encontrar nenhuma informação sobre isso no site heroku.
Shubham Chaudhary

254

Eu usei o extremamente popular Whenever em projetos que dependem muito de tarefas agendadas, e é ótimo. Ele fornece um bom DSL para definir suas tarefas agendadas em vez de precisar lidar com o formato crontab. No README:

Sempre que é uma gema Ruby, fornece uma sintaxe clara para escrever e implantar tarefas cron.

Exemplo do 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
Se for executado a cada minuto, o ambiente será reiniciado todas as vezes, o que pode ser caro. Parece que o github.com/ssoroka/scheduler_daemon evita isso.
Lulalala #

3
+1 para manter a configuração cron com seu sistema de controle de versão
brittohalloran

3
Eu acho que essa é a melhor solução. Se você estiver usando trilhos, acho melhor escrever tudo em trilhos. Com essa abordagem, você também pode esquecer a tarefa cron ao alterar servidores, ela se move com o aplicativo.
Adrian Matteo

Existe um ótimo Railscast sobre Sempre que é realmente útil (também existe uma versão gratuita mais antiga).
Aceofbassgreg 11/04

@ Tony, Sempre que é basicamente uma linguagem específica de domínio para escrever trabalhos cron. Ele é compilado na sintaxe cron regular no servidor rails e o cron é o que executa as tarefas que você especificar (geralmente através do corredor rails).
Greg

19

Em nosso projeto, usamos pela primeira vez sempre que gem, mas enfrentamos alguns problemas.

Em seguida, mudamos para a gema RUFUS SCHEDULER , que acabou sendo muito fácil e confiável para agendar tarefas no Rails.

Nós o usamos para enviar e-mails semanais e diários e até para executar algumas tarefas periódicas de rake ou qualquer outro método.

O código usado é o seguinte:

    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 saber mais: https://github.com/jmettraux/rufus-scheduler


1
Adequado para o rufus, como eu o usei em projetos simples de rubi ou em aplicativos rails completos.
Paulo Fidalgo

8
Você poderia ser um pouco mais específico sobre os problemas com o Whenever?
Duke

a resposta mais ótima de todos os tempos
Darlan Dieterich

17

Supondo que suas tarefas não demorem muito para serem concluídas, basta criar um novo controlador com uma ação para cada tarefa. Implemente a lógica da tarefa como código do controlador e, em seguida, configure um cronjob no nível do sistema operacional que use o wget para chamar a URL desse controlador e a ação nos intervalos de tempo apropriados. As vantagens deste método são você:

  1. Tenha acesso total a todos os seus objetos Rails, como em um controlador normal.
  2. Pode desenvolver e testar exatamente como você faz ações normais.
  3. Também pode invocar suas tarefas adhoc a partir de uma simples página da web.
  4. Não consuma mais memória acionando processos adicionais de ruby ​​/ rails.

12
Como impedir que outras pessoas acessem esta tarefa? Se a tarefa que leva a CPU e a chama com frequência causa problemas.
22410 sarunw

44
Eu sei que isso foi há um tempo atrás, mas essa definitivamente não é mais a melhor maneira de fazer trabalhos cron. Por que acessar a interface da Web, violando o que a interface realmente representa, quando existem muitas outras maneiras de acessar o ambiente Rails?
Matchu

6
A qualificação "assumindo que suas tarefas não demoram muito para ser concluída" parece uma ENORME. Não seria melhor usar uma abordagem geralmente mais útil, e não apenas nos casos em que as tarefas são muito rápidas? Dessa forma, você não está constantemente reavaliando se essa ou aquela tarefa precisa ser reescrita usando uma abordagem diferente.
Iconoclasta

77
Esta pergunta antiga é o principal resultado do google para "rails cron". Esta resposta está longe de ser a melhor abordagem. Por favor, veja as outras respostas para sugestões mais sensatas.
21711 Jim Carvin

2
Não é o melhor caminho. Você tem muitas outras maneiras de acessar o ambiente do Rails por meio de uma tarefa cron sem chamar um serviço REST. Abordagem Rake é certamente melhor
Equipamento para engraxar os

10

As tarefas de script / runner e rake são perfeitas para serem executadas como tarefas cron.

Aqui está uma coisa muito importante que você deve lembrar ao executar tarefas cron. Eles provavelmente não serão chamados no diretório raiz do seu aplicativo. Isso significa que todos os seus requisitos para arquivos (ao contrário de bibliotecas) devem ser feitos com o caminho explícito: por exemplo, File.dirname (__ FILE__) + "/ other_file". Isso também significa que você precisa saber como chamá-los explicitamente de outro diretório :-)

Verifique se o seu código suporta a execução de outro diretório com

# 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

Além disso, os trabalhos cron provavelmente não são executados como você, portanto, não dependa de nenhum atalho inserido no .bashrc. Mas isso é apenas uma dica cron padrão ;-)


Você pode executar o trabalho como qualquer usuário (basta definir a entrada crontab para o usuário desejado), mas você está certo de que o perfil e os scripts de logon não serão executados e você não iniciará no diretório inicial. Por isso, é comum para iniciar o comando com um "cd", como mostrado no comentário de @ luke-Franci
Tom Wilson

10

O problema de quando (e cron) é que ele recarrega o ambiente de trilhos toda vez que é executado, o que é um problema real quando suas tarefas são frequentes ou têm muito trabalho de inicialização a ser feito. Eu tive problemas na produção por causa disso e devo avisá-lo.

O agendador Rufus faz isso por mim ( https://github.com/jmettraux/rufus-scheduler )

Quando tenho trabalhos longos para executar, uso-o com delayed_job ( https://github.com/collectiveidea/delayed_job )

Eu espero que isso ajude!


10

Sou um grande fã do resque / resque scheduler . Você não pode apenas executar tarefas repetidas do tipo cron, mas também tarefas em horários específicos. A desvantagem é que ele requer um servidor Redis.


10

Isso é interessante, ninguém mencionou o Sidetiq . É uma boa adição se você já estiver usando o Sidekiq.

O Sidetiq fornece uma API simples para definir trabalhadores recorrentes para o Sidekiq.

Jó terá a seguinte aparência:

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

  recurrence { hourly.minute_of_hour(15, 45) }

  def perform
    # do stuff ...
  end
end

8

Ambos irão funcionar bem. Eu costumo usar script / runner.

Aqui está um exemplo:

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

Você também pode escrever um script Ruby puro para fazer isso se carregar os arquivos de configuração corretos para se conectar ao seu banco de dados.

Uma coisa a ter em mente se a memória é preciosa é que o script / runner (ou uma tarefa Rake que depende do 'ambiente') carregará todo o ambiente do Rails. Se você apenas precisar inserir alguns registros no banco de dados, isso utilizará a memória que você realmente não precisa. Se você escrever seu próprio script, poderá evitar isso. Ainda não precisei fazer isso, mas estou considerando.


8

Use Craken (tarefas cron centralizadas em rake)


1
escrevendo trabalhos do cron é tão difícil, baixe melhor uma jóia para que
f0ster

1
não é difícil - mas tê-los armazenados no git e sempre atualizados na implantação é uma grande vantagem quando se trabalha em equipe.
Thibaut Barrère


3

Aqui está como eu configurei minhas tarefas cron. Eu tenho um para fazer backups diários do banco de dados SQL (usando rake) e outro para expirar o cache uma vez por mês. Qualquer saída é registrada em um arquivo log / cron_log. Meu crontab fica assim:

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

A primeira tarefa do cron faz backups diários do banco de dados. O conteúdo de cron_tasks é o seguinte:

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

A segunda tarefa foi configurada posteriormente e usa script / runner para expirar o cache uma vez por mês (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}"

Eu acho que eu poderia fazer backup do banco de dados de outra maneira, mas até agora funciona para mim :)

Os caminhos para o rake e o ruby ​​podem variar em diferentes servidores. Você pode ver onde eles estão usando:

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

3

Usar algo Sidekiq ou Resque é uma solução muito mais robusta. Ambos suportam a repetição de trabalhos, exclusividade com um bloqueio, monitoramento e programação REDIS.

Lembre-se de que o Resque é um projeto morto (não é mantido ativamente), portanto o Sidekiq é uma alternativa muito melhor. Ele também tem mais desempenho: o Sidekiq executa vários trabalhadores em um único processo multithread enquanto o Resque executa cada trabalhador em um processo separado.


Essa é uma resposta correta. Muitos podem esquecer os bons recursos que o sidekiq ou o resque estão fornecendo, como interface da web para monitorar o que está acontecendo: número de trabalhos em execução, com falha ou agendados, reiniciá-los facilmente, bloquear para trabalhadores únicos, limitar e limitar etc.
Dmitry Polushkin

3

Recentemente, criei alguns trabalhos cron para os projetos nos quais tenho trabalhado.

Achei que a gema Clockwork era muito útil.

require 'clockwork'

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

Você pode até agendar seu trabalho em segundo plano usando esta gema. Para documentação e ajuda adicional, consulte https://github.com/Rykian/clockwork



2

Uma vez eu tive que tomar a mesma decisão e estou muito feliz com essa decisão hoje. Use o resque scheduler porque não apenas um redis separado removerá a carga do seu banco de dados, você também terá acesso a muitos plugins como o resque-web, que fornece uma ótima interface de usuário. À medida que o seu sistema se desenvolve, você terá mais e mais tarefas para agendar e poderá controlá-las a partir de um único local.


1

Provavelmente, a melhor maneira de fazer isso é usar o rake para escrever as tarefas que você precisa e apenas executá-lo via linha de comando.

Você pode ver um vídeo muito útil em railscasts

Veja também outros recursos:


Tentei sem sucesso usar a sintaxe neste tutorial. A tarefa não foi executada.
Tass

1

Eu usei um relógio e funciona muito bem para mim. Também existe uma clockworkdgema que permite que um script seja executado como um daemon.


0

Não tenho muita certeza, acho que depende da tarefa: quantas vezes executar, quão complicada e quanta comunicação direta com o projeto rails é necessária etc. Acho que se houvesse apenas "One Best Way" para fazer algo , não haveria tantas maneiras diferentes de fazer isso.

No meu último trabalho em um projeto do Rails, precisávamos fazer uma correspondência em lote (convites para pesquisas, não spam), que deveria enviar as mensagens planejadas sempre que o servidor tivesse tempo. Acho que íamos usar as ferramentas daemon para executar as tarefas de rake que eu havia criado.

Infelizmente, nossa empresa teve alguns problemas financeiros e foi "comprada" pelo principal rival para que o projeto nunca fosse concluído, então não sei o que eventualmente teríamos usado.


0

Eu uso o script para executar o cron, que é a melhor maneira de executar um cron. Aqui está um exemplo para cron,

Abra o CronTab -> sudo crontab -e

E Cole as linhas abaixo:

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

Aqui está um formato cron, irá ajudá-lo

::CRON FORMAT::

tabela 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 isso ajude você :)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.