何年か前に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
1 | 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
1 | sudo service redis start |
デフォルトだと127.0.0.1:6379でredisサーバは待ち受ける。
(設定ファイルは/etc/redis.confにある。)
redis-test.php
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 | <?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(); |
1 | php redis- test .php |
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | echoのテスト 成功 setのテスト 成功 getのテスト 成功 deleteのテスト 成功 incr, decrのテスト 子プロセス終了 wait終了 成功 setexのテスト まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する まだキーが存在する キーが消えた 成功 後掃除 成功 |
php-redisのredisオブジェクトはforkするとおかしな事になるみたい。
例えば以下のようなコードの場合
(fork前に生成したredisオブジェクトを、fork後の親プロセスと子プロセスでそのまま使った場合)
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | $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://…