PHP+memcache+Repcachedを試してみた 再び
先日のエントリにid:yasui0906さんからコメント頂きました。ありがとうございます:-)
あれ。1.2が出たからやろうと思っていた。。。あれ?あれ?前のエントリは1.0でやっちゃってるorz
オブジェクトをserializeしないでsetすると、スレーブでgetしたら文字列になってしまう問題があることに、つい最近気が付きまして、その修正をしたrepcached-1.2をリリースしたばかりだったりします(^^;
http://dsas.blog.klab.org/archives/51198643.html
ということで、今度こそRepcached1.2.0で試してみました。環境やインストール手順、起動方法、試したスクリプトも同じです。念のため、PHPスクリプトだけ再掲。
<?php $ttl = 60; $servers = array(array('ip' => '192.168.98.128', 'port' => 11211), array('ip' => '127.0.0.1', 'port' => 11211)); //$servers = array(array('ip' => '127.0.0.1', 'port' => 11211)); $memcache = new Memcache(); foreach ($servers as $server) { $ip = $server['ip']; $port = $server['port']; if (@$memcache->pconnect($ip, $port)) { printf('connected to %s:%s<br/>', $ip, $port); break; } } if (!$memcache->getStats()) { die('failed to connect to server'); } $version = $memcache->getVersion(); echo 'server version: '.$version.'<br/>'; $get_result = $memcache->get('key99'); echo 'cached data:<br/>'; var_dump($get_result); if (!$get_result) { for ($i = 0; $i < 100; $i++) { $obj = new stdClass; $obj->str = 'test' . $i; $obj->int = $i; $obj->date = microtime(); $obj = new Exception('exception' . $i); if (!$memcache->set('key' . $i, $obj, false, $ttl)) { die ('failed to save data'); } } echo 'saved data (TTL=' . $ttl . ')<br/>'; } var_dump($memcache->getStats()); var_dump($memcache->getExtendedStats()); foreach ($servers as $server) { $ip = $server['ip']; $port = $server['port']; var_dump(@$memcache->getServerStatus($ip, $port)); }
この状態でブラウザから2回アクセスすると次のように表示され、
connected to 192.168.98.128:11211 server version: 1.2.4 cached data: object(stdClass)[2] public 'str' => string 'test99' (length=6) public 'int' => int 99 public 'date' => string '0.74148000 1205903024' (length=21) :
ここでMasterをCTRL+Cで落として再度アクセスすると、
connected to 127.0.0.1:11211 server version: 1.2.4 cached data: object(stdClass)[2] public 'str' => string 'test99' (length=6) public 'int' => int 99 public 'date' => string '0.74148000 1205903024' (length=21) :
うおっ。serializeされてない!すばらしー!
で、ここでふと。。。
- Repcachedってシングルマスタ・レプリケーションっすよね。。。
- ということは、落ちたMasterが復帰した場合、今のスクリプトでは旧Master(現Slave)側に接続してしまう
- データ更新しても、現Master(旧Slave)にはデータ反映されないよなぁ
気になったので、以下のフローをテスト。
- Master、Slaveを起動
- ブラウザから数回アクセス
- Masterを停止(SlaveがMasterになる)
- ブラウザから数回アクセス
- Masterを起動(SlaveがMasterのままで、MasterがSlaveになる)
- ブラウザから数回アクセス(現Slave側にアクセス&データ更新してしまう)
- 旧Masterを停止(現Slave側)
- ブラウザから数回アクセス
- キャッシュ時間は600秒
- 必要な情報だけ出力
- 毎回memcachedにデータを書き込む
- 書き込んだ$obj->dateの値を表示する
ようにしたものです。
<?php $ttl = 600; $servers = array(array('ip' => '192.168.98.128', 'port' => 11211), array('ip' => '127.0.0.1', 'port' => 11211)); //$servers = array(array('ip' => '127.0.0.1', 'port' => 11211)); $memcache = new Memcache(); foreach ($servers as $server) { $ip = $server['ip']; $port = $server['port']; if (@$memcache->pconnect($ip, $port)) { printf('connected to %s:%s<br/>', $ip, $port); break; } } if (!$memcache->getStats()) { die('failed to connect to server'); } $get_result = $memcache->get('key99'); echo 'cached data:' . $get_result->date . '<br/>'; for ($i = 0; $i < 100; $i++) { $obj = new stdClass; $obj->str = 'test' . $i; $obj->int = $i; $obj->date = microtime(); if (!$memcache->set('key' . $i, $obj, false, $ttl)) { die ('failed to save data'); } } echo 'date:' . $obj->date. '<br/>';
書き込んだdateの値が、次回キャッシュされたデータとして表示されれば問題なし、という判断ができるという「目diff」処理です。。。実行結果は以下の通り。
connected to 192.168.98.128:11211 cached data:0.84800300 1205904637 date:0.53991800 1205904638 connected to 192.168.98.128:11211 cached data:0.53991800 1205904638 date:0.04531000 1205904644 (Master停止) connected to 127.0.0.1:11211 cached data:0.04531000 1205904644 date:0.44386100 1205904654 connected to 127.0.0.1:11211 cached data:0.44386100 1205904654 date:0.07622700 1205904713 (Master復帰。以下、旧Master) connected to 192.168.98.128:11211 cached data:0.07622700 1205904713 date:0.18975900 1205904736 connected to 192.168.98.128:11211 cached data:0.18975900 1205904736 date:0.48710500 1205904742 (旧Master停止) connected to 127.0.0.1:11211 cached data:0.07622700 1205904713 date:0.62147200 1205904758
ああ、やっぱり。最後のcached dataが1つ前のdateになってない。。。
という事は、接続しているのがMasterなのかSlaveなのかを判断する必要があるという事ですな。で、ちょっと見てみると、getStatsメソッドから返される配列にキー「replication」があり、その値が「MASTER」「BACKUP」のいずれかになるらしいので、それを利用させてもらう事にしました。ということで、接続部分は
<?php : foreach ($servers as $server) { $ip = $server['ip']; $port = $server['port']; if (@$memcache->pconnect($ip, $port)) { $stats = $memcache->getStats(); if ($stats['replication'] === 'MASTER') { printf('connected to %s:%s<br/>', $ip, $port); break; } else { printf('%s:%s is not MASTER, so close connection<br/>', $ip, $port); @$memcache->close(); } } } if (!$memcache->getStats()) { die('failed to connect to server'); }
な感じで、「MASTERじゃなかったら切断して次」として再度実行。
connected to 192.168.98.128:11211 cached data:0.94455000 1205905491 date:0.36900700 1205905492 connected to 192.168.98.128:11211 cached data:0.36900700 1205905492 date:0.34132900 1205905519 (Master停止) connected to 127.0.0.1:11211 cached data:0.34132900 1205905519 date:0.64040700 1205905528 connected to 127.0.0.1:11211 cached data:0.64040700 1205905528 date:0.22376500 1205905539 (Master復帰。以下、旧Master) 192.168.98.128:11211 is not MASTER, so close connection connected to 127.0.0.1:11211 cached data:0.22376500 1205905539 date:0.46198200 1205905553 192.168.98.128:11211 is not MASTER, so close connection connected to 127.0.0.1:11211 cached data:0.46198200 1205905553 date:0.92956800 1205905612 (旧Master停止) connected to 127.0.0.1:11211 cached data:0.92956800 1205905612 date:0.77474200 1205905621 connected to 127.0.0.1:11211 cached data:0.77474200 1205905621 date:0.69529600 1205905643 (旧Master復帰) 192.168.98.128:11211 is not MASTER, so close connection connected to 127.0.0.1:11211 cached data:0.69529600 1205905643 date:0.68547800 1205905652 192.168.98.128:11211 is not MASTER, so close connection connected to 127.0.0.1:11211 cached data:0.68547800 1205905652 date:0.33831200 1205905656 (旧Slave停止) 192.168.98.128:11211 is not MASTER, so close connection connected to 127.0.0.1:11211 cached data:0.68547800 1205905652 date:0.33831200 1205905656 connected to 192.168.98.128:11211 cached data:0.24341500 1205905668 date:0.30667700 1205905672
結構良い感じです:-)
まあ、これをスクリプト側でやらなくて済むとかなり楽なので、
期待してますっ!
repcachedはシングルマスタ構成なので、memcachedのクライアントは接続するサーバとしてただひとつのIPアドレスを指定する必要があります。
しかし、repcachedがフェイルオーバした場合、マスタの役割を果たすサーバが変わってしまうので、サーバのIPアドレスも変わってしまいます。
これではフェイルオーバのたびにクライアントの設定を変えなくてはならず、「ラクな運用」を信条とするDSASでは受け入れられません。
この問題については、後日、別エントリでスマートな解決方法を考えてみたいと思っていますのでご期待くださいませ。
追記(2008/05/22 14:48)
マルチマスタに対応したRepcached2.0については、PHP+memcache+Repcachedを試してみた みたび - Do You PHP はてなを参照してください。このエントリのような面倒な事はしなくて済むようになっています。