Redisを使って排他制御するwrapperコマンド Redis-Setlock をPerlとGoで書いた

しばらく前に作って書きそびれていましたが、Yokohama.pm #10 でLTしたのでエントリもあげます。

LTのスライドはこちら ⇒ Redis-Setlockを書いたはなし

なにをするもの?

setlockコマンドのロック処理をRedisサーバで行うもの」です。

setlockはflockを使ってロックを獲得したら引数に渡されたコマンドをexecする、daemontools付属のwrapperコマンドで、cronでコマンド実行するときなど多重実行を制御する場合に重宝します。

flockだとホストをまたいだロック処理が行えないため、その部分をRedisを使った排他制御に置き換えたものを書いてみました。

使い方

$ redis-setlock [-nNxX] KEY program [ arg ... ]
$ go-redis-setlock [-nNxX] KEY program [ arg ... ]

--redis (Default: 127.0.0.1:6379): redis-host:redis-port
--expires (Default: 86400): The lock will be auto-released after the expire time is reached.
--keep: Keep the lock after invoked command exited.
-n: No delay. If KEY is locked by another process, redis-setlock gives up.
-N: (Default.) Delay. If KEY is locked by another process, redis-setlock waits until it can obtain a new lock.
-x: If KEY is locked, redis-setlock exits zero.
-X: (Default.) If KEY is locked, redis-setlock prints an error message and exits nonzero.

オプションと使い方は基本的に setlock と同じで、使用する Redis サーバ、ロックを自動開放するための expire、終了してもロックを(expireまで)開放しないというオプションが追加されています。

実装

Redisでの排他制御公式ドキュメントに書いてあるものをそのまま使用しています

SET に NX フラグを付けると、「すでにKeyが存在しない場合のみSETが成功する」という動作をするためにロックに使えるんですね。

setlockはロックを獲得したらそのまま単にexecveすることで、プロセス終了時にロックが解放されるという素敵な実装なのですが、Redisを使う場合は自分でRedisサーバにコマンドを発行して開放処理を行う必要があるため、execではなく子プロセスをforkしています。

その関係で、(Go版での) 標準入出力の扱いやシグナルハンドリングなど、いろいろ泥臭いコードが増えてしまっているのが美しくないところです……

Go版はクロスコンパイル可能なので、本来はWindows版も作れるはずなのですが、子プロセスの終了コードを取得する処理でWindowsだと動かないコードがあるらしく、コンパイルできませんでした。自分では使わないからまあいいか、とそのままになっています。

Go版は http://fujiwara.github.io/go-redis-setlock/ からバイナリをダウンロードするだけで動くので deploy がお手軽だと思います。

Perl版にはライブラリとして使用できるインターフェースも用意してあります。

my $redis = Redis->new( server => 'redis.example.com:6379' );
if ( my $guard = Redis::Setlock->lock_guard($redis, "key", 60) ) {
   # got a lock!
   ...
   # unlock at guard destroyed.
}
else {
   # could not get lock
}

複数ホスト間で気軽に排他制御できるので、是非お試しください。