何年か前にNoSQL界の新星Redisを使ってみたときは(そんときはPythonから使ってみた)セットアップにちょっと手間取ったような気がするのですが、最近だとずいぶん楽になりましたね。
その間にRedisもNoSQL界の新星からNoSQL界の雄にランクアップした感があります。
ということでPythonからだとどんな感じで使えるのかという事はもう知ってるので、今回はとりあえずphpからRedisを使ってみました。
OS: Amazon Linux 2012.09
php: Package php-5.3.18-1.27.amzn1.x86_64
sudo yum --enablerepo=epel install redis php-redis
入ったパッケージ
Package redis-2.4.10-1.el6.x86_64
Package php-redis-2.2.2-5.git6f7087f.el6.x86_64
sudo service redis start
デフォルトだと127.0.0.1:6379でredisサーバは待ち受ける。
(設定ファイルは/etc/redis.confにある。)
redis-test.php
<?php $get_redis_connect = function() { $redis = new Redis(); if ($redis->connect('localhost', 6379)) { return $redis; } exit("Error: redisに接続できない\n"); }; $test = function($title, $testfunc) { print "\n${title}\n"; if ($testfunc()) { print "成功\n"; } else { print "失敗\n"; } sleep(1); }; $redis = $get_redis_connect(); $test("echoのテスト", function() use(&$redis) { return $redis->echo('えこー') == 'えこー'; }); $test("setのテスト", function() use(&$redis) { return $redis->set('hoge', 'fuga'); }); $test("getのテスト", function() use(&$redis) { return $redis->get('hoge') == 'fuga'; }); $test("deleteのテスト", function() use(&$redis) { $redis->delete('hoge'); return $redis->exists('hoge') === false; }); $redis->close(); $test("incr, decrのテスト", function() use(&$get_redis_connect) { // マルチプロセスで同時にincr, decrしして整合性をチェックする // (非同期に同じ回数incr, decrして元の値に戻るかどうかチェックする) $pid = pcntl_fork(); if ($pid == -1) { exit("Error: forkできない\n"); } if ($pid) { // 親プロセスではインクリメント // Redisオブジェクトはプロセス間で共有出来ないのでここで作成する $redis = $get_redis_connect(); for ($i = 0; $i < 10000; $i++) { $redis->incr('hoge'); } $redis->close(); while (pcntl_waitpid($pid, $status) != -1) { sleep(1); } print "wait終了\n"; } else { // 子プロセスではデクリメント // Redisオブジェクトはプロセス間で共有出来ないのでここで作成する $redis = $get_redis_connect(); for ($i = 0; $i < 10000; $i++) { $redis->decr('hoge'); } $redis->close(); exit("子プロセス終了\n"); } $redis = $get_redis_connect(); // redisでは値は全て内部で文字列として格納されてるので // 型も含めてチェックするとき(===を使うとき)は気をつける。 $ret = $redis->get('hoge') === '0'; $redis->close(); return $ret; }); $redis = $get_redis_connect(); $test("setexのテスト", function() use(&$redis) { // キーに有効期限をセットして観察してみる $ex = 10; // 有効期限(秒) $time_start = microtime(true); $redis->setex('hoge', $ex, 'fuga'); while ($redis->exists('hoge')) { print "まだキーが存在する\n"; sleep(1); } $time_end = microtime(true); print "キーが消えた\n"; return abs($time_end - $time_start - $ex) < 1.5; }); $test("後掃除", function() use(&$redis) { $redis->delete('hoge'); return true; }); $redis->close();
php redis-test.php
echoのテスト 成功 setのテスト 成功 getのテスト 成功 deleteのテスト 成功 incr, decrのテスト 子プロセス終了 wait終了 成功 setexのテスト まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する キーが消えた 成功 後掃除 成功
php-redisのredisオブジェクトはforkするとおかしな事になるみたい。
例えば以下のようなコードの場合
(fork前に生成したredisオブジェクトを、fork後の親プロセスと子プロセスでそのまま使った場合)
$redis = new Redis(); if (!$redis->connect('localhost', 6379)) { exit("Error: redisに接続できない\n"); } $pid = pcntl_fork(); if ($pid == -1) { exit("Error: forkできない\n"); } if ($pid) { // 親プロセスではインクリメント for ($i = 0; $i < 10000; $i++) { $redis->incr('hoge'); } while (pcntl_waitpid($pid, $status) != -1) { sleep(1); } print "wait終了\n"; } else { // 子プロセスではデクリメント for ($i = 0; $i < 10000; $i++) { $redis->decr('hoge'); } exit("子プロセス終了\n"); }
子プロセスではRedisサーバーに未接続という意味でRedisExceptionがスローされる。
が、これはマルチプロセス関係なく未接続の状態でコマンドを発行しようとした場合と同じエラーメッセージが表示されるので分かりやすい。
分かりづらかったのは親プロセス側ではコマンドが適用されたりされなかったりする点。
例では1万回incrコマンドを発行してるが、あとで結果を見ると7千いくつとかまちまちで中途半端な数までしかカウントされてない。
コマンドが部分的に失敗してるような感じだが、失敗したときにRedisExceptionが発生するわけでもなく、incrコマンドの戻り値も全部確認したわけではないが、if文にぶっこんで偽になるような値も返ってきてない様子。
最初「Redisのincr, decrってアトミックだと思ってたけど違うの!?そんなバカな」なんて思ったりしたけど、当然そんな事はなくてphp-redisの使い方が悪かっただけみたい。
ちょっと試行錯誤した結果、fork前に生成したredisオブジェクトを使わず、親プロセス・子プロセス共にfork後に新たにredisオブジェクトを生成することで回避できた。
「php-redisをforkして使う場合は、各プロセスで初期化しよう。」というのを本日の教訓としたいと思います。
(こんなテスト的なコード以外でforkと組み合わせることは一生なさそう)
最近のコメント
名前
しゅごい
Jane Doe
FYI Avoid Annoying Unexpe…
Jane Doe
ご存じとは思いますが、whileには、”~の間”と…
peta_okechan
針金みたいなパーツを引っ張ると外れます。 他の方の…
虎徹ファン交換
虎徹の標準ファンを外す際に、どのようにして外されま…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…
花粉症対策2019 – 日曜研究室
[…] 花粉症対策についてはこれまで次の記事を書いてきました。https://…