cron の代替となる機能を単独で提供してくれる軽量のツール/ライブラリです。
clockwork のメインサイトを読んだだけではわからないことがたくさんあったので、 clockwork のソースを実際に読んで情報を得ている部分がありますし、自分で考えて書いた部分も結構あります。
何か意見があればコメント欄に記載をお願いします。
関連記事
- Hello clockwork on Heroku
- clockwork を heroku で使う方法を紹介しています。
clockwork をインストールする
以下のコマンドを実行して clockwork gem をインストールします。
gem install clockwork
バージョン 0.7.0 からネイティブコードを含むパッケージを使うようになったため、コンパイルエラーがでる場合は DEVELOPMENT KIT のセットアップを行う必要があります。
もしくは以下の方法で 0.6.2 をインストールする方法もあります。
gem install clockwork --version "= 0.6.2"
clockwork が提供してくれるメソッドについて
clockwork は以下のメソッドを提供してくれます。
メソッド名 | 概要 |
every | 定期実行するジョブを定義するメソッド。 |
handler | ジョブ実行時に呼び出される処理を定義するメソッド。 |
Numeric#seconds, Numeric#minutes, Numeric#hour, Numeric#day | 時間の指定を書きやすくしてくれる拡張です。 |
configure*1 | Clockwork の初期化時に呼び出され、Clockwork のマルチスレッド/ロギング/タイムゾーンに関する設定を定義するメソッド。 |
これらのメソッドを組合せて定期実行をプログラムし、clockwork コマンドでそれを実行します。
サンプルソース
require 'clockwork' module Clockwork handler do |job| puts "Running #{job}" end every(10.seconds, 'frequent.job') every(3.minutes, 'less.frequent.job') every(1.hour, 'hourly.job') every(1.day, 'midnight.job', :at => '00:00') end
解説
基本
基本は、以下の通りです。
- handler にジョブ実行時の処理を定義する。
- every でジョブを定義する。
- every でジョブの実行タイミングを指定する際に Numeric#seconds, Numeric#minutes, Numeric#hour, Numeric#day を使う。
- module Clockwork は handler, every を呼びやすくするためのもので、特別な事情がなければそのままで良い(必要な状況は後述)。
every でジョブ定義
サンプルでは4つのジョブを定義しています。
実行タイミング | ジョブ名 |
10 秒毎に実行 | frequent.job(頻繁なジョブ) |
3 分毎に実行 | less.frequent.job(ちょっと頻繁なジョブ) |
1 時間毎に実行 | hourly.job(1時間毎のジョブ) |
毎日夜中の12時に実行 | midnight.job(真夜中のジョブ*2) |
handler でジョブ実行
every で定義したタイミングで handler のブロックが呼び出されます。
- ジョブを実行できる handler は1つだけ。
- job は every の 2 つ目の引数で指定したオブジェクト。
サンプルでは全ての every 呼び出しで文字列を渡しているので 10 秒毎に
Running frequent.job
と表示されます。他のジョブも同様です。
1.day を除き、他のジョブは起動時にまず最初に実行され、そこから時間の計測がはじまります。
every や handler の名前が他と衝突するとき
もしも every や handler が他の機能の名前と衝突する場合は、以下のようにして使います。
- module Clockwork を削除する。
- every の代わりに Clockwork::every を使う。
- handler の代わりに Clockwork::handler を使う。
clockwork を動かす
以下のコマンドを実行して clockwork を起動します。
clockwork clock.rb
- clock.rb はよく使われる名前なだけで、ファイルの名前は任意である。
- clockwork コマンドは指定されたファイルを Ruby のプログラムとして実行し、handler と every で指定された情報に基づきジョブを実行し続けてくれる。
clockwork を止める
SIGINT シグナルを送れば Exit と表示されて正常終了します。Windows なら Ctrl + C を押してください。
その他、StandardError 以外の例外が発生すると止まります。
基本的なプログラムの書き方
handler にすべてのジョブが集まります。
ジョブが1種類だけであれば handler に単純に処理を書けば良いですが、複数種類のジョブを扱うには工夫が必要です。
複数のジョブを処理するにはいくつかの書き方があります。以下に事例を挙げたいと思います。
- handler ブロックに case 文を書いて処理を振り分ける。
- lambda/Proc/Fiber/ を使って every メソッド呼び出し毎に処理を定義し、handler ブロッグで呼び出す。
- 個々の処理をクラスとして定義し every メソッド呼び出し毎に対応するオブジェクトを生成して渡し、ポリモフィズムを使って呼び出し先を自動で分ける。
case 文の利用
require 'clockwork' module Clockwork handler do |job| case job when 'frequent.job' # 10 秒毎の処理 when 'less.frequent.job' # 3 分毎の処理 when 'hourly.job' # 1 時間毎の処理 when 'midnight.job' # 夜中の処理(なんだかエロい…) end end every(10.seconds, 'frequent.job') every(3.minutes, 'less.frequent.job') every(1.hour, 'hourly.job') every(1.day, 'midnight.job', :at => '00:00') end
単純な場合は、これでも良いと思います。
lambda/Proc/Fiber の利用
lambda と Proc の呼び出しは call という同じ名前のメソッドなので一緒に使えます。
しかし、Fiber の呼び出しは resume なので lambda と Proc と一緒に使うことはできないので、
- lambda と Proc で全てのジョブを定義する。
- Fiber で全てのジョブを定義する。
のいずれかの場合に利用できる方法です。
番外編として
- Proc と Fiber を case で振り分けて呼び出すよう handler を定義する。
という方法もありますが、恐らく 1 番の方法を利用する機会が多いと思うので、その事例を示します。
- every 呼び出しに lambda や Proc を使って処理を定義する。
- handler で call を呼び出して、それぞれのブロックを呼び出す。
- fiber を使う場合、handler では call ではなく resume を使う。
require 'clockwork' module Clockwork handler do |job| job.call end # こういう書き方もOK every( 10.seconds, lambda do # 10 秒毎の処理 end ) # こういう書き方もOK less_frequent_job = lambda do # 3 分毎の処理 end every(3.minutes, less_frequent_job) # こういう書き方もOK every( 1.hours, Proc.new do # 1 時間毎の処理 end ) every( 1.day, lambda do # 夜中の処理(どう書こうとなんだかエロい…) end, :at => '00:00' )
ポリモフィズムの利用
この方法が一番汎用的なので、プログラムが大きくなればこの方法を採ることになると思います。
- 個々のジョブをファイル/クラスに分けて定義する。
- clock.rb から require する。
- クラスで定義するメソッドの名前は同じにする(事例では call としているが、統一されていれば何でも良い。lambda や Proc と組合せて使うことも考慮すると call が一番いいと思う)。
- every でそれぞれのオブジェクトを生成して渡す。
- handler で call を呼ぶ。
- clock.rb
require 'clockwork' require 'frequent_job' require 'less_frequent_job' require 'hourly_job' require 'midnight_job' module Clockwork handler do |job| job.call end every(10.seconds, FrequentJob.new) every(3.minutes, LessFrequentJob.new) every(1.hour, HourlyJob.new) every(1.day, MidnightJob.new, :at => '00:00') end
- frequent_job.rb
class FrequentJob def call # 10 秒毎の処理 end end
- less_frequent_job.rb
class LessFrequentJob def call # 3 分毎の処理 end end
- hourly_job.rb
class HourlyJob def call # 1 時間毎の処理 end end
- midnight_job.rb
class MidnightJob def call # 夜中の処理(ファイルが分けられ密閉度上昇…、さらにエロい…) end end
備考
clockwork はシングルスレッドで動作するため、並列に動作させたい場合は :thread オプションを利用する
clockwork はシングルスレッドで動作します。handler の呼び出しもシングルスレッドです。
従って、handler 内でループなどの長期処理を実行した場合、その他のジョブの実行は待たされることになります。
every で指定した個々のジョブの実行タイミングは、前回実行を開始した時間を基点としてカウントされます。
特定の長期ジョブにより handler が占有され、開始時刻を過ぎたジョブは、長期ジョブの終了を契機に一気に実行されることになります。
このように特定のジョブにより handler が占有されないようにするには、スレッドを利用する必要があります。
version 0.5.1 からジョブ定義に :thread オプションを指定することで別スレッドでハンドラを動かす機能が追加されたようです。
例えば以下のように書くことで該当のジョブを別スレッドにできます。
# 長期処理 every(10.seconds, 'long_time.job', :thread => true)
最大スレッド数は configure にて設定が可能です。
configure do |config| config[:max_threads] = 100 end
起動時にスレッド数が最大数を超えた場合、ジョブは実行されず、ログにその旨のメッセージが出力されます。
config[:max_threads] の初期値は 10 です。
この max_threads の値は、Clockwork が管理するスレッドだけではなく、全てのスレッド数を監視しているので、自分で独自に内部処理でスレッドを作っている場合はそのスレッドもカウントされます。
以降の情報は古いので不要かもしれませんが残しておきます。version 0.5.0 以前にマルチスレッドの機能はないので、自分でそれぞれのジョブを handler 内でスレッドにして実行する必要がありました。
require 'clockwork' module Clockwork handler do |job| Thread.new do # ジョブごとの処理を実行する。 # Thread.pass を適度に呼び、他のスレッドに処理を渡す。 # 同一のジョブが多重で実行されないようにするには適宜 Mutex を利用する。 end end every(10.seconds, 'frequent.job') every(3.minutes, 'less.frequent.job') every(1.hour, 'hourly.job') every(1.day, 'midnight.job', :at => '00:00') end
コメント
本ページの内容に関して何かコメントがある方は、以下に記入してください。
最新の10件を表示しています。 コメントページを参照