Rails的cron job:最佳实践?

306

在Rails环境中运行定时任务,最好的方式是什么?是Script/runner还是Rake?我希望每隔几分钟运行一次任务。


153
对于从Google搜索进入这里的人,请查看更好的方法而不仅仅是接受的答案。 - jrdioko
4
“每当”这个答案似乎比被接受的答案更为合理,而被接受的答案是一种老套路。 - Rob
2
请注意,至少有一个答案假定您已安装了某个特定的 gem。 - Tass
这里总结了一些(我发现的)好的实践方法:https://www.wisecashhq.com/blog/writing-reliable-cron-jobs。 - Thibaut Barrère
在许多情况下,cron作业是一种不好的代码气味。最好通过sidekiq/resque(或其他后台工作程序)编写调度程序,或者编写一个守护进程(功能较少且可监视)。 cron作业至少有几个问题:1)为一个实例加锁很麻烦; 2)监视不能轻松完成; 3)异常处理必须手动编写; 4)不易重启; 5)以上所有问题均可以通过后台工作程序轻松解决。 - Dmitry Polushkin
Rake = Ruby make。这是一个非常强大的工具。我建议观看Ryan Bates在Railscast上的介绍 - http://railscasts.com/episodes/66-custom-rake-tasks - Yoni
21个回答

267

我在依赖于定时任务的项目中使用了极为流行的Whenever,它非常棒。它提供了一种漂亮的DSL来定义您的定时任务,而不是必须处理crontab格式。从README中可以看到:

Whenever是一个Ruby gem,提供了一个清晰易懂的语法,用于编写和部署cron job。

下面是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

23
如果每分钟运行一次,环境将每次重新启动,这可能很耗费资源。看起来https://github.com/ssoroka/scheduler_daemon可以避免这种情况。 - lulalala
3
赞同将 cron 配置文件与版本控制系统一起保存。 - brittohalloran
3
我认为这是最好的解决方案。如果你在使用Rails,我认为最好全部用Rails编写。采用这种方法,当更换服务器时,你也可以忘记关于cron任务的一切,它将随应用程序移动。 - Adrian Matteo
有一个非常棒的Railscast关于Whenever,非常有帮助(也有一个旧的免费版本)。 - aceofbassgreg
@Tony,Whenever 是用于编写 Cron 作业的特定域语言。它会在您的 Rails 服务器上编译成常规的 Cron 语法,并且 Cron 是执行您指定的作业的工具(通常是通过 rails runner 运行)。 - Greg

114

我正在使用rake方法(heroku支持),

具体是在一个名为lib/tasks/cron.rake的文件中..

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

要在命令行中执行,只需输入"rake cron"。然后可以根据需要将此命令放入操作系统的cron/task scheduler中。 更新:这是一个相当古老的问题和答案!以下是一些新信息:
  • 我提到的heroku cron服务已经被Heroku Scheduler取代。
  • 对于频繁的任务(尤其是您想避免Rails环境启动成本的情况),我的首选方法是使用系统cron调用一个脚本,该脚本将(a)触发安全/私有的Webhook API以在后台调用所需任务或(b)直接将任务排队到您选择的排队系统中。

13
注意:最近我正在使用 whenever(请参见Jim Garvin的答案),但是运行rake任务的原始cron条目可能如下所示:30 4 * * * /bin/bash -l -c 'cd /opt/railsapp && RAILS_ENV=production rake cron --silent' - tardate
1
你怎么从控制台调用它?我尝试了 load "#{Rails.root}/lib/tasks/cron.rake"rake cron 命令,但是遇到了 NameError: undefined local variable or method `cron' for main:Object 错误。 - B Seven
3
这种方法的问题在于:environment依赖。我们有一个非常庞大的Rails应用程序,启动需要很长时间,我们的Rake每分钟都会被调用,消耗更多资源来启动Rails环境而不是执行任务。我希望能够在cron中调用已经启动的Rails环境,介于_controller_方法和_rake environment_方法之间。 - fguillen
我只是非常好奇与后台宝石(如delayed job或sidekiq)相比的性能差异。当您想要组织大量后台任务时,我确实看到了它的好处。 - dtc
@dtc 通过cron运行rake非常浪费,因为每次都需要加载rails环境。dj或sidekiq保持一个实例运行。因此,我更喜欢使用cron来触发一个webhook,以便将所需的作业加入队列。这是轻量级的,并且您可以利用cron作为调度程序和dj/sidekiq作为作业队列的好处。 - tardate
显示剩余4条评论

22

在我们的项目中,我们最初使用了 whenever gem,但遇到了一些问题。

然后我们转而使用 RUFUS SCHEDULER gem,在Rails中安排任务非常容易且可靠。

我们用它来发送每周和每日的邮件,甚至用于运行一些周期性的rake任务或任何方法。

使用的代码类似于:

    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

了解更多信息:https://github.com/jmettraux/rufus-scheduler


1
我支持使用Rufus,因为我既用它来开发简单的Ruby项目,也用它来开发完整的Rails应用。 - Paulo Fidalgo
9
请问您能否就遇到的Whenever问题更具体地说明一下? - Duke
最棒的答案 - Darlan Dieterich

20

假设您的任务完成时间不太长,只需创建一个新控制器,为每个任务创建一个操作。将任务逻辑作为控制器代码实现,然后在操作的适当时间间隔设置使用wget调用此控制器和操作的URL的操作系统级别的cronjob。此方法的优点是您:

  1. 完全可以像普通控制器一样访问所有Rails对象。
  2. 可以像处理普通操作一样进行开发和测试。
  3. 也可以从简单的网页中随时调用您的任务。
  4. 无需启动额外的ruby/rails进程而消耗更多的内存。

12
如何防止他人访问此任务?如果此任务频繁占用CPU会导致问题。 - sarunw
46
我知道这是一段时间以前的事情,但这绝对不再是执行 cron 作业的最佳方式。为什么要通过 Web 接口来访问 Rails 环境,在有大量其他访问方式的情况下违反接口的真正用途呢? - Matchu
7
“假设您的任务不需要太长时间完成”这个限定条件似乎非常重要。使用一种更普遍适用的方法会更好,而不仅仅是在任务非常快速的情况下使用。这样,您就不必不断重新评估是否需要使用不同的方法重写这个或那个任务。 - iconoclast
79
这个古老的问题是“Rails Cron”在谷歌上排名最高的结果。这个答案远非最佳解决方案。请查看其他回答,以获取更明智的建议。 - Jim Garvin
2
虽然我同意这不是最好的方法,但我并没有像大多数人那样彻底反对它。只要你记住一些事情(保持执行时间短,确保行动不能被每个人触发),这仍然是一个快速启动周期性任务的好方法。如需进一步阅读,我已经写了一篇文章 - Michael Trojanek
显示剩余9条评论

12
每当(和 cron)的问题在于每次执行时都会重新加载 Rails 环境,这对于频繁执行或需要大量初始化工作的任务来说是一个真正的问题。我曾经因此在生产环境中遇到过问题,必须警告你。
Rufus scheduler 为我做了这件事(https://github.com/jmettraux/rufus-scheduler)。
当我需要运行长时间的作业时,我会与 delayed_job 一起使用它(https://github.com/collectiveidea/delayed_job)。
希望这可以帮到你!

11

很有趣,没有人提到Sidetiq。 如果你已经在使用Sidekiq,那么这是一个不错的补充。

Sidetiq为Sidekiq定义定期工作提供了简单的API。

工作将如下所示:

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

  recurrence { hourly.minute_of_hour(15, 45) }

  def perform
    # do stuff ...
  end
end

11

我是 resque/resque调度器 的忠实粉丝。你不仅可以运行类似于cron的定期任务,还可以在特定时间运行任务。缺点是需要一个Redis服务器。


11

使用 script/runnerrake 任务作为 cron 作业是完全可以的。

当运行 cron 作业时,有一件非常重要的事情需要记住。它们可能不会从您的应用程序根目录中调用。这意味着所有文件(而不是库)的引用都应该使用显式路径完成,如 File.dirname(__FILE__) + "/other_file"。这也意味着您必须知道如何从另一个目录显式地调用它们 :-)

检查您的代码是否支持从另一个目录运行:

# 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

此外,cron作业可能不会以您的身份运行,因此不要依赖于您在.bashrc中设置的任何快捷方式。但这只是一个标准的cron技巧 ;-)


你可以将作业以任何用户的身份运行(只需为您想要的用户设置crontab条目),但是您是正确的,配置文件和登录脚本不会运行,并且您不会在您的主目录中启动。因此,常见做法是像@luke-franci的评论中所示,以“cd”命令开始执行。 - Tom Wilson

9

使用 Craken(基于Rake的定时任务)


1
编写定时任务非常困难,最好下载一个宝石包来解决。 - f0ster
1
不难,但是将它们存储在 Git 中,并且在部署时始终保持最新状态对于团队合作非常有帮助。 - Thibaut Barrère

9
两者都可以正常工作。我通常使用script/runner。
以下是一个示例:
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
如果加载正确的配置文件连接到数据库,您还可以编写纯Ruby脚本来完成此操作。
要牢记的一件事是,如果内存很宝贵,则script/runner(或依赖于“环境”的Rake任务)将加载整个Rails环境。如果您只需要将一些记录插入数据库,则会使用您实际上没有的内存。如果您编写自己的脚本,可以避免这种情况。我实际上还没有需要这样做,但我正在考虑。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接