Do You PHP?    
Search Engine Optimization  php5 powerd  Valid XHTML 1.0!  Valid CSS!  このサイトのはてなブックマーク数 



last updated
2005/05/25

counter hits
since 1999/11/06


Turck MMCacheを導入してみる - Turck MMCache APIを使ってPHPでServletContext!?

caution[2005/02/11] ITProのPHPウォッチによるとプロジェクトは停止状態にあり、新プロジェクトとしてeAcceleratorプロジェクトが開始されています。近いうちにまた調べてみようと思います。

ここ最近紹介してきたTurck MMCacheですが、今回はTurck MMCache APIと呼ばれる拡張関数を試してみました。目的は「データのキャッシュ」です。

MMCacheをインストールすると以下の拡張関数が利用できるようになります(バージョン2.4.6で確認)。これらのうち、mmcache_put関数はPHP変数を保存する事ができ、キャッシュした値はmmcache_get関数で取得することができます。これらをうまく使うとサーバ全体でデータのキャッシュを行うことが可能です。

  • mmcache
  • mmcache_cache_output
  • mmcache_cache_page
  • mmcache_cache_result
  • mmcache_encode
  • mmcache_gc
  • mmcache_get
  • mmcache_load
  • mmcache_lock
  • mmcache_put
  • mmcache_rm
  • mmcache_rm_page
  • mmcache_set_session_handlers
  • mmcache_unlock

以下はキャッシュデータを作成するスクリプトで、Oracleのdept表から全データを取得し連想配列のままキャッシュさせる、というものです。

●setup.php

<?php
    $conn = OCILogon("scott", "tiger", "orcl");
    $stmt = OCIParse($conn,"SELECT * FROM dept ");
    OCIExecute($stmt);

    $ret = array();
    $record = array();
    while (OCIFetchInto($stmt, $record, OCI_ASSOC + OCI_RETURN_NULLS)) {
        $ret[] = $record;
    }

    OCIFreeStatement($stmt);
    OCILogoff($conn);

    /**
     * メモリに書き込む
     */
    mmcache_lock('dept');
    mmcache_put('dept', $ret);
    mmcache_unlock('dept');
?>
dept表のデータをメモリに書き込みました。

扱い方としてはファイルの場合と似ており、

  • ロックの取得
  • データの書き込み
  • ロックの解放

というありがちな手続きになります。ただし、データに対する名前(キー)を指定する必要があります。この名前は後ほどキャッシュデータを取得する際に必要になります。

一方の読み込み側ですが、以下のようにmmcache_get関数を使用するだけです。存在しない名前を指定した場合はnullが返りますので、エラー判定などに利用できると思います。

●test.php

<?php
    $dept = mmcache_get('dept');
    var_dump($dept);
?>

上記を踏まえ以下のようなキャッシュ管理クラスを作ってみました。元ネタはPHP4でデザインパターン(番外編:Intercepting Filterパターン)で作成したFilterManagerクラスです。キャッシュするデータとしては文字列・配列・オブジェクトで、それぞれ別のFilter(CacheBuilder)として定義してあります。

●setup.php

<?php
require_once('jp/ne/hi_ho/pat/dimension/Abstract.phl');
?>
<?php
/**
 * キャッシュデータを作成するクラス(Intercepting Filterパターンの
 * Filterに相当)のためのインターフェース
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class CacheBuilder
{
    /**
     * データを作成し、それを返す
     *
     * @return キャッシュデータ
     */
    function build() { Abstract::set('build'); }

    /**
     * キャッシュデータに割り当てる名前(キー)を返す
     *
     * @return キー
     */
    function getName() { Abstract::set('getName'); }
}

/**
 * キャッシュデータ管理クラス
 * Intercepting FilterパターンのFilterManagerに相当する
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class CacheManager
{
    var $chain_;

    /**
     * コンストラクタ
     */
    function CacheManager()
    {
        $this->chain_ = new CacheBuilderChain();
    }

    /**
     * CacheBuilderを追加する
     *
     * @param $builders CacheBuilderオブジェクト、あるいはそれらの配列
     */
    function addCacheBuilder($builders)
    {
        if (!is_array($builders)) {
            $builders = array($builders);
        }
        foreach ($builders as $idx => $builder) {
            $this->chain_->addCacheBuilder($builder);
        }
    }

    /**
     * キャッシュデータを構築する
     */
    function process()
    {
        $this->chain_->build();
    }

    /**
     * キャッシュデータを取得する
     *
     * @param $name キャッシュデータの名前(キー)
     * @return キャッシュデータ
     */
    function get($name)
    {
        $data = mmcache_get(strtolower($name));
        return $data;
    }
}

/**
 * キャッシュデータを作成するクラスのチェーン
 * Intercepting FilterパターンのFilterChainに相当する
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class CacheBuilderChain
{
    var $next_;

    /**
     * コンストラクタ
     */
    function CacheBuilderChain()
    {
        $this->next_ = array();
    }

    /**
     * CacheBuilderオブジェクトを追加する
     *
     * @param $next CacheBuilderオブジェクト
     */
    function addCacheBuilder($next){
        array_push($this->next_, $next);
    }

    /**
     * キャッシュデータを作成し、メモリに格納する
     */
    function build()
    {
        reset($this->next_);
        foreach ($this->next_ as $idx => $obj) {
            $data = $obj->build();

            /**
             * 取得したデータをキャッシュに書き込む
             */
            if ($data) {
                $name = strtolower($obj->getName());

                mmcache_lock($name);
                mmcache_put($name, $data);
                mmcache_unlock($name);
            }
        }
    }
}

/**
 * キャッシュデータ(文字列)を作成する
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class StringCacheBuilder extends CacheBuilder
{
    function build()
    {
        return 'Copyright &copy; 2004 Hideyuki Shimooka. All rights reserved.';
    }

    function getName() { return 'string'; }

}

/**
 * キャッシュデータ(配列)を作成する
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class ArrayCacheBuilder extends CacheBuilder
{
    function build()
    {
        $ret = array();
        $ret[] = '最初のデータです';
        $ret[] = '2番目データです';
        $ret[] = '3番目のデータです';
        return $ret;
    }

    function getName() { return 'array'; }

}

/**
 * キャッシュデータ(オブジェクト)を作成する
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class ObjectCacheBuilder extends CacheBuilder
{
    function build()
    {
        $obj = new stdclass();
        $obj->x1 = 10;
        $obj->y1 = 15;
        $obj->x2 = 20;
        $obj->y3 = 25;
        return $obj;
    }

    function getName() { return 'object'; }

}

/**
 * キャッシュデータ(DBから連想配列)を作成する
 *
 * @author SHIMOOKA Hideyuki <shimooka@doyouphp.jp>
 * @create 2004/11/20
 * @copyright (C)2004 Hideyuki Shimooka. All rights reserved.
 */
class DBCacheBuilder extends CacheBuilder
{
    function build()
    {
        $conn = OCILogon('scott', 'tiger', 'orcl');
        $stmt = OCIParse($conn,'SELECT * FROM dept ');
        OCIExecute($stmt);

        $ret = array();
        $record = array();
        while (OCIFetchInto($stmt, $record)) {
            $ret[] = $record;
        }

        OCIFreeStatement($stmt);
        OCILogoff($conn);

        return $ret;
    }

    function getName() { return 'db'; }

}
?>
<?php
/**
 * キャッシュ管理クラスをインスタンス化します
 */
$manager = new CacheManager();

/**
 * addCacheBuilderメソッドには、CacheBuilderオブジェクト、
 * あるいはCacheBuilderオブジェクトの配列を渡すことができます
 */
$manager->addCacheBuilder(new StringCacheBuilder());

$builders = array(new ArrayCacheBuilder(),
                  new ObjectCacheBuilder());
$manager->addCacheBuilder($builders);

//$manager->addCacheBuilder(new DBCacheBuilder());

/**
 * キャッシュデータを構築します
 */
$manager->process();

echo 'setup successful';
?>
<hr>
<?php
    show_source($_SERVER['SCRIPT_FILENAME']);
?>

キャッシュしたデータの取得は、以下のようにCacheManagerクラスのgetメソッドを使用します。setup.phpにアクセスしたPCとは別のPCからアクセスしてみたりすると良いかも知れません。

●main.php

<?php
require_once('CacheManager.phl');
?>
<?php
/**
 * キャッシュ管理クラスをインスタンス化します
 */
$manager = new CacheManager();

/**
 * 指定するキャッシュデータの名前は、大文字小文字を区別しません
 */
var_dump($manager->get('string'));
var_dump($manager->get('ARRAY'));
var_dump($manager->get('Object'));
//var_dump($manager->get('db'));

?>
<hr>
<?php
    show_source($_SERVER['SCRIPT_FILENAME']);
?>

キャッシュされたデータですが、設定によってはApacheの再起動を行うと消えてしまいます。具体的には、php.iniで

●php.ini
mmcache.keys="shm_only"

とした場合です。デフォルト値は

●php.ini
mmcache.keys="shm_and_disk"

で、mmcache.cache_dir指定されたディレクトリにも書き込まれます。このため、Apacheの再起動を行ってもキャッシュが消えることはありません。当然、前述のディレクトリ内の「mmcache-user-*」というファイルを削除してしまうと、キャッシュもクリアされます。また、MMCacheの導入ページの最後で紹介したWeb向けAPIで「Clear」ボタンを押下することでキャッシュをクリアする事ができます。

ここまで作ってみて使い道を考えてみると、今回のサンプルは初期化Servlet的(?)なものでしたが、あるタイミングでDBなどに書き出す仕組みを作って、もっと積極的にアクセスや更新が集中するようなデータやオブジェクトを格納しても良いかも知れません。

ここ最近一番のヒットかも知れない > MMCache



About This Site |  Privacy Policy |  Contact
Copyright © 1999 - 2005 by Hideyuki SHIMOOKA all rights reserved.