おひさしブリーフ、かっぱです。

tl;dr

下図のように cron ジョブで定期的にスクリプトを生成して実行させようとした時、スクリプト完了する前に次の cron ジョブが走ってスクリプトが生成されて、そのスクリプトが実行されてしまうような状況に遭遇してどうしたもんかなと悩んでいたら ts というツールを見つけたので試してみた。

20160807074518

memo

ts とは

スクリプトやコマンドを ts コマンド経由で実行することで、それらをジョブとしてキューに放り込んで順次実行してくれるツール。冒頭の構成に ts を加えると下図のようになり、cron の開始時間とスクリプトの実行時間の依存関係は切り離される。

20160807082925

導入

CentOS 6 に導入するにはソースコードを取得してビルドする必要がある。

$ cat /etc/redhat-release
CentOS release 6.7 (Final)

$ sudo yum install gcc
$ wget http://vicerveza.homeunix.net/~viric/soft/ts/ts-0.7.6.tar.gz
$ tar zxvf ts-0.7.6.tar.gz
$ cd ts-0.7.6
$ make
$ mkdir ~/bin
$ cp ts ~/bin/ts

サンプル

以下のような雑なシェルスクリプトを cron で実行させる。

$ cat demo.sh
#!/bin/sh

DATE=`date +%s`
echo "echo $DATE && sleep 300" > /tmp/test.sh.$DATE
${HOME}/bin/ts logger -t ts_demo "スクリプトを作成しました"
sleep 3
logger -t ts_demo "スクリプトを実行します"
${HOME}/bin/ts sh /tmp/test.sh.$DATE
${HOME}/bin/ts -d logger -t ts_demo "スクリプトの実行が完了しました"

$ crontab -l
*/3 * * * * /home/centos/demo.sh >/dev/null 2>&1

暫く放置してから ts コマンド(ts -l でも同等)を実行すると以下のように ts コマンド経由で実行したコマンド(スクリプト)がジョブとしてキューイングされている。

$ ts
ID   State      Output               E-Level  Times(r/u/s)   Command [run=1/1]
0    running    /tmp/ts-out.b5xJat                           sh /tmp/test.sh.1470470326
1    queued     (file)                                       [0]&& logger -t ts_demo スクリプトの実行が完了しました
2    queued     (file)                                       logger -t ts_demo スクリプトを作成しました
3    queued     (file)                                       sh /tmp/test.sh.1470470342
4    queued     (file)                                       [3]&& logger -t ts_demo スクリプトの実行が完了しました

さらに暫く放置してからジョブ一覧を確認すると…

$ ts -l
ID   State      Output               E-Level  Times(r/u/s)   Command [run=1/1]
6    running    /tmp/ts-out.FSSSnL                           sh /tmp/test.sh.1470470581
7    queued     (file)                                       [6]&& logger -t ts_demo スクリプトの実行が完了しました
8    queued     (file)                                       logger -t ts_demo スクリプトを作成しました
9    queued     (file)                                       sh /tmp/test.sh.1470470761
10   queued     (file)                                       [9]&& logger -t ts_demo スクリプトの実行が完了しました
11   queued     (file)                                       logger -t ts_demo スクリプトを作成しました
12   queued     (file)                                       sh /tmp/test.sh.1470470941
13   queued     (file)                                       [12]&& logger -t ts_demo スクリプトの実行が完了しました
14   queued     (file)                                       logger -t ts_demo スクリプトを作成しました
15   queued     (file)                                       sh /tmp/test.sh.1470471121
16   queued     (file)                                       [15]&& logger -t ts_demo スクリプトの実行が完了しました
0    finished   /tmp/ts-out.b5xJat   0        300.00/0.00/0.00 sh /tmp/test.sh.1470470326
1    finished   /tmp/ts-out.js0Oq6   0        0.00/0.00/0.00 [0]&& logger -t ts_demo スクリプトの実行が完了しました
2    finished   /tmp/ts-out.6Jjrf6   0        0.00/0.00/0.00 logger -t ts_demo スクリプトを作成しました
3    finished   /tmp/ts-out.fotDb6   0        300.00/0.00/0.00 sh /tmp/test.sh.1470470342
4    finished   /tmp/ts-out.8y6fdL   0        0.00/0.00/0.00 [3]&& logger -t ts_demo スクリプトの実行が完了しました
5    finished   /tmp/ts-out.lMTR8K   0        0.00/0.00/0.00 logger -t ts_demo スクリプトを作成しました

finished となっているのは完了したジョブ。logger で吐いているログを見ると…

$ sudo tail -f /var/log/messages
Aug  6 08:03:04 ip-xxx-xx-xx-xx ts_demo: スクリプトを実行します
Aug  6 08:03:49 ip-xxx-xx-xx-xx ts_demo: スクリプトの実行が完了しました
Aug  6 08:03:49 ip-xxx-xx-xx-xx ts_demo: スクリプトを作成しました
Aug  6 08:06:04 ip-xxx-xx-xx-xx ts_demo: スクリプトを実行します
Aug  6 08:08:49 ip-xxx-xx-xx-xx ts_demo: スクリプトの実行が完了しました
Aug  6 08:08:49 ip-xxx-xx-xx-xx ts_demo: スクリプトを作成しました
Aug  6 08:09:04 ip-xxx-xx-xx-xx ts_demo: スクリプトを実行します
Aug  6 08:12:04 ip-xxx-xx-xx-xx ts_demo: スクリプトを実行します
Aug  6 08:13:49 ip-xxx-xx-xx-xx ts_demo: スクリプトの実行が完了しました
Aug  6 08:13:49 ip-xxx-xx-xx-xx ts_demo: スクリプトを作成しました

ts の操作

以下は ts の主な操作。

  • ジョブの一覧確認
$ ts -l
  • 完了したジョブをジョブ一覧から削除
$ ts -C
  • ジョブの一括削除(タスクスプーラーサーバープロセスを kill する)
$ ts -K
  • 前のジョブが完了したら実行する
    以下のように -d オプションをつけてジョブを登録することで、${command-1} が正常終了したら ${command-2}が実行される。
$ ts ${command-1}
$ ts -d ${command-2}
  • ジョブの標準出力を確認
$ ts -t ${JOB_ID}

その他、以下が ts のヘルプ。

$ ts -h
usage: ts [action] [-ngfmdE] [-L ] [-D ] [cmd...]
Env vars:
  TS_SOCKET  the path to the unix socket used by the ts command.
  TS_MAILTO  where to mail the result (on -m). Local user by default.
  TS_MAXFINISHED  maximum finished jobs in the queue.
  TS_MAXCONN  maximum number of ts connections at once.
  TS_ONFINISH  binary called on job end (passes jobid, error, outfile, command).
  TS_ENV  command called on enqueue. Its output determines the job information.
  TS_SAVELIST  filename which will store the list, if the server dies.
  TS_SLOTS   amount of jobs which can run at once, read on server start.
  TMPDIR     directory where to place the output files and the default socket.
Actions:
  -K       kill the task spooler server
  -C       clear the list of finished jobs
  -l       show the job list (default action)
  -S [num] get/set the number of max simultaneous jobs of the server.
  -t [id]  "tail -n 10 -f" the output of the job. Last run if not specified.
  -c [id]  like -t, but shows all the lines. Last run if not specified.
  -p [id]  show the pid of the job. Last run if not specified.
  -o [id]  show the output file. Of last job run, if not specified.
  -i [id]  show job information. Of last job run, if not specified.
  -s [id]  show the job state. Of the last added, if not specified.
  -r [id]  remove a job. The last added, if not specified.
  -w [id]  wait for a job. The last added, if not specified.
  -k [id]  send SIGTERM to the job process group. The last run, if not specified.
  -u [id]  put that job first. The last added, if not specified.
  -U   swap two jobs in the queue.
  -B       in case of full queue on the server, quit (2) instead of waiting.
  -h       show this help
  -V       show the program version
Options adding jobs:
  -n       don't store the output of the command.
  -E       Keep stderr apart, in a name like the output file, but adding '.e'.
  -g       gzip the stored output (if not -n).
  -f       don't fork into background.
  -m       send the output by e-mail (uses sendmail).
  -d       the job will be run only if the job before ends well
  -D   the job will be run only if the job of given id ends well.
  -L  name this task with a label, to be distinguished on listing.
  -N  number of slots required by the job (1 default).

最後に

ts の使いドコロ

分散キューシステムでは無いので、一台のサーバー上でしか動かせないのは注意が必要
遅延処理はしたいけど、処理の実行順序は出来るだけ守りたい場合
SQS や RabbitMQ 等を導入する程でもなくて、一台のサーバーで完結するようなバッチ処理

今まで知らずにすいません

本当にすいません。

元記事はこちら

cron + α が欲しい時には ts(Task Spooler) のご利用をご検討下さいというメモ