複数サーバーで同時実行される可能性のあるスクリプトの排他制御を実装するのに、DDLockd をつかってはどうかというアイデアをいただいたので、DDLockd について調べてみた(なんかロックするやつ、ぐらいのことしかしらなかった)。
DDLockd とは
Danga Distributed Lock Daemon の略で、まさに今回やろうとしていた複数サーバーで実行されるアプリの排他制御を行うための仕組み、みたいなのを提供してくれる daemon。Perl で書かれている。Danga の名前が指し示すとおり LiveJournal で使われている(た?)。TypePad でもほんのりつかっていたり。
コードはこのあたりにおちていた。
今回は code.sixapart.com の trunk の r46 を使ってみた。
server のインストール
daemon も Perl で書かれてる。trunk/server/ddlockd をもってきてどっか適当におく。とりあえず今回は $HOME/bin においた。依存してる CPAN モジュールは Danga::Socket くらい。
client のインストール
trunk/api/perl に一式はいっているので、svn からとってきたらおきまりの、
% perl Makefile.PL
% make
% make test
% sudo make install
でインストール。t/01_all.t のテストが localhost で daemon が動いているの前提になっているので、インストール前に、
% ddlockd --daemon
で、起動しておく。もしくは test 無視して install。Perl 以外の client モジュールはいまのところナッシング。
server 起動
起動自体は、さっきの、
% ddlockd --daemon
で、7002 番ポートを listen するやつが起動する。port オプションでお好みの port に変更可。 type オプションでロック情報をどこで持つかというのが設定できたりで、以下3つがつかえるぽい。
- internal
- daemon プロセスのメモリ。デフォルト。
- dlmfs
- ファイル。/dlm/ddlockd ってディレクトリに lock 名でファイルを作って管理する。
- 実際にはここに作られるファイルがサーバー間でシェアされる必要があるのでNFSな領域とかになるかな。
- dbi
- データベース。
- hostname でDBサバ名を指定。ただ実際には localhost しかみていない...orz。とはいっても指定しないとエラーになる。
- DBI->connet('dbi:mysql:dbname=sixalock', ...) と書かれているので、MySQL に "sixalock" という DB を作っとく必要がる。いやなら変更してしまう。
- table オプションでテーブル名を指定する必要がある。
- スキーマは定義ファイルはないけど、コードを見る限り "name" っていう 文字列型のカラムが1こあるテーブルがひとつあればいいぽい。要ユニークインデックス。
ちなむと、TypePad ではデフォルトの internal で使ってた。一番無難と思われる(LJ は知らない…)。
使い方
クライアントのほうの使い方は SYNOPSIS どおり。こんなんで確認した。
#!/usr/bin/env perl
use strict;
use warnings;
use DDLockClient ();
my $client = DDLockClient->new(servers => [ 'localhost:7002' ]);
my $Lock = $client->trylock('lock-test')
or die "Failed to lock";
while (1) {
print time."\n";
sleep 5;
}
DESTROY {
$Lock && $Lock->release;
};
telnet でいじる
memcached のように text な protocol なので telnet コマンドでもいろいろ確認できる。使えるコマンドは以下のとおり。
- status - 状態もろもろ表示
- load - よくわからん
- trylock - 指定文字列で lock を作る
- releaselock - 指定文字列の lock を解放
- locks - 保持している lock を表示(dlmfs の場合は実装がなくて使えない)
daemon を internal type で起動、先のクライアントをずっと動かしてる状態でひととおりやってみた。
% telnet localhost 7002
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
status
STATUS: OK
SUCCESSES: 13
FAILURES: 4
RUNTIME: 2987
LOAD: 1
load
LOAD: 0
locks
LOCKS:
lock-test = Client::Internal(R): open to 127.0.0.1:48757
trylock lock=foo
OK
locks
LOCKS:
foo = Client::Internal(R): open to 127.0.0.1:40654
lock-test = Client::Internal(R): open to 127.0.0.1:48757
releaselock lock=foo
OK
locks
LOCKS:
lock-test = Client::Internal(R): open to 127.0.0.1:48757
ふむ。
まとめ
ざっとさわってみて、daemontools で ddlockd を動かしておけば結構安心して使えそうな気がする。ddlockd 突然死でロックがきえるというのは “dlmfs” or “dbi” type で起動しとけば防げる。ただ、この場合ロックファイル or ロックレコードがのこったままになるので、そいつらをいったん消してあげないといけない、という問題がある。これを防ぐには client 側でがんばるしかなく、release でエラったら成功するまでひたすらがんばるとかしないといけない(ほかなんかいい方法あるかな?)。まぁこれは ddlockd 使わなくてもファイル使った排他制御やるなら同じことがいえる(はず)。
多くの daemon なプログラムが採用している .pid ファイル使った多重起動防止な仕組みの複数サバ対応版としての役割としては必要十分かなと。LJ/TypePad で長らく使ってるって実績もあるし。とかとか、いろいろ考えたりもしたけれど、結局 memcached でできちゃうのよね、多重起動防止なら…。
とりあえず今回はすでに memcached が動いてる/使える環境なので、そっちを使う方向でw 結局すでにあった実装を再利用できる形にした(NAS 上に <:NFSLock> でロックファイルつくる)。