金曜日, 12 1 月 2007

memcachedを使ったPHPのシングルトン実装 この記事(memcachedを使ったPHPのシングルトン実装)を「はてなブックマーク」に追加 この記事をクリップ! この記事(memcachedを使ったPHPのシングルトン実装)を「del.icio.us」に追加

« 高鷲スノーパーク | Main | Apacheチューニング: ロギングを高速化する »
PHPのクラスに備わっているstaticはJava(Servlet)のそれとは違いHTTPのリクエストが完了すると破棄されてしまいます。
そのためstaticフィールドを使ったシングルトンの実装を行ったとしてもリクエストがある度にインスタンスが生成され独立したプロセスから同一のインスタンスにアクセスすることは不可能です。

そこで今回memcachedを利用しPHPの各プロセスから同一のインスタンスを参照できるようにしてみたいと思います。
といってもシリアライズさせているので厳密には別のインスタンスになりますが…。

ちなみにmemcachedとはオブジェクトをメモリにキャッシュさせるPHPとは独立したサーバプログラムです。
利用できる言語はPHPだけに限らずPerl、Ruby、Java、Pythonなどにも対応しています。
インストールは./configure && make && make installで簡単ですが、あらかじめlibeventのインストールしておく必要があります。

次にPHPからmemcachedに接続するためのAPIを持つPECL拡張モジュールをインストールします。
# pear install http://pecl.php.net/get/memcache-2.1.0.tgz
それからphp.iniにエクステンションの記述を追加。
extension="(memcahce.soまでのパス)/memcache.so"
これで準備完了です。

今回シングルトンとして作るクラスは設定情報などを保持するものと想定して作りました。
以下がそのクラスのソースです。
class Repository
{
        private static $memcache;

        private $map;
        private $updated;

        public static function getInstance()
        {
                self::$memcache=new Memcache();
                self::$memcache->connect("localhost",11211);

                $repository=self::$memcache->get("repository");
                if(!$repository)
                        $repository=new Repository();

                return $repository;
        }

        private function __construct()
        {
                $this->map=array();
                $this->updated=false;
        }

        public function set($key,$val)
        {
                $this->map[$key]=$val;
                $this->updated=true;
        }

        public function get($key)
        {
                return $this->map[$key];
        }

        public function __destruct()
        {
                if($this->updated)
                {
                        $this->updated=false;
                        self::$memcache->set("repository",$this);
                }
                self::$memcache->close();
        }
}
説明するほどのものでもないですが。まずgetInstance()内でMemcacheオブジェクトを通じてmemcachedに接続し、"repository"というキーでオブジェクトが存在しているか調べます。
存在していればmemcachedから取得したオブジェクトをそのまま返し、存在していなければ新たにインスタンスを生成して返します。
これで永続化されたオブジェクトを取得することが出来るのですが、その後オブジェクトの状態を変更してもJavaのように参照を保持しているわけではないので永続化されたオブジェクトは更新されません。
そこで__destructを使いオブジェクトが破棄されるタイミングでもし更新されていればmemcachedにオブジェクトを書き出す処理を行っています。
$repository=Repository::getInstance();
$repository->set("test",123);
上ような処理だけでリクエストが終了しても状態は保たれたままその後他のプロセスも同じオブジェクトを参照することが出来ます。
しかもネットワーク越しに同一のオブジェクトを共有できるためWebサーバに負荷が増えてきても容易にスケール出来るのはかなりのメリットではないでしょうか。
ただし今回の実装は排他制御を行っていないのでそのままでは使えませんが…。

参考までに他のシングルトンの実装としてオブジェクトをシリアライズしてファイルに保存するパターンとデータベース(MySQL)に保存するパターンを作りベンチマークを取ってみました。

requests/secリモート接続
memcached334
ファイル439×
DB(MySQL)277
※ab -c 100 -n 1000の結果です。
※MySQLはHEAPテーブルを使用しています。

これを見るとローカルからだけの参照ならファイルにシリアライズさせるパターンが一番速いですが、リモートからの参照となるとMySQLよりmemcachedに保存した方が速いようです。
作成したファイル版とデータベース版のソースも一応置いておきます。(→ファイル版、→データベース版)

パフォーマンスは悪くないのでオブジェクトに限らず画像やテキストなどキャッシュさせれば負荷を軽減させるのに役立つのではないでしょうか。slashdotやmixiも使っているようですし。
Posted by tsujitako at 5:42 午前 in PHP/