Do You PHP はてブロ

Do You PHPはてなからはてブロに移動しました

PHP+memcache+Repcachedを試してみた 再び

先日のエントリid:yasui0906さんからコメント頂きました。ありがとうございます:-)


オブジェクトをserializeしないでsetすると、スレーブでgetしたら文字列になってしまう問題があることに、つい最近気が付きまして、その修正をしたrepcached-1.2をリリースしたばかりだったりします(^^;
http://dsas.blog.klab.org/archives/51198643.html

あれ。1.2が出たからやろうと思っていた。。。あれ?あれ?前のエントリは1.0でやっちゃってるorz
ということで、今度こそ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されてない!すばらしー!


で、ここでふと。。。

気になったので、以下のフローをテスト。

  1. Master、Slaveを起動
  2. ブラウザから数回アクセス
  3. Masterを停止(SlaveがMasterになる)
  4. ブラウザから数回アクセス
  5. Masterを起動(SlaveがMasterのままで、MasterがSlaveになる)
  6. ブラウザから数回アクセス(現Slave側にアクセス&データ更新してしまう)
  7. 旧Masterを停止(現Slave側)
  8. ブラウザから数回アクセス

PHPスクリプトは先のスクリプト

  • キャッシュ時間は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」のいずれかになるらしいので、それを利用させてもらう事にしました。ということで、接続部分は

<?phpforeach ($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 はてなを参照してください。このエントリのような面倒な事はしなくて済むようになっています。