phpダイナミックコンテンツファイルキャッシュの設計と効率的な実現


サイトのパフォーマンスの向上を考えると、条件反射はキャッシュを思い浮かべ、キャッシュのほとんどの技術者はmemcachedやredisを思い浮かべる.
確かに、memcachedとredisには他にも多くのNoSQLのような効率的で伸縮性のあるキャッシュソリューションがあります.
ここでは軽量レベルのファイルキャッシュの実装について話しています.多くの場合、彼はNoSQLキャッシュよりも問題を解決するのに役立ちます.
一.テスト結果を見てみましょう.
データ量10 W程度の電子商取引プラットフォームがあり、当時の日アクセスIPは5 W程度であった.
私はその中の1つのアクティブなページに対して圧力テストをしました:(多くのデータベースクエリーがあって、サーバーの構成:WinNT/IS/2 G/2.4 GHZデュアルコア)
1.裸走:320 rps(取った平均値)
2.有効なファイルキャッシュを使用する:510 rps(平均値をとる)
3.memcached:560 rps(平均値をとる)
現在のサイトでは、そのキャッシュスキームを選択しますか??
redisにはmemcachedの全体的なキャッシュ速度がファイルキャッシュよりずっと速くないこともあります.彼らの利点は分布式やクラスタができ、同時数と応答速度の導関数がファイルキャッシュよりも満足できることです. 
また、ファイルキャッシュが問題をよりよく解決し、安価である場合があることも説明した.
二.ファイルキャッシュの設計と実装:
1.キャッシュデータの決定:
キャッシュ:1つのプロセスの計算を経なければならない結果を格納し、一定の時間内に格納結果を用いてプロセスの実行を回避し、プロセスの性能を向上させる.(もちろん、ストレージ結果の取得には迅速な応答が必要です)
最初のステップは、キャッシュが必要な結果を決定することです.もちろん、これはキャッシュのアプリケーションの範疇ですが、この問題を事前に考慮すると、私たちの設計と実現に役立ちます.
    (1). データベースクエリーの結果    (2). 関数の計算結果    (3). 1ページの実行結果    ......
2.記憶媒体の決定:
キャッシュするデータが得られたら、次に、結果をどのメディアにキャッシュするかを決定することに重点を置く.    (1). ディスク(2).メモリ
コストを抜きにして、十分なメモリがあれば、メモリは不二の選択であり、アクセス速度はディスクの100 W倍である.しかし、ほとんどの場合、私たちはそんなに多くのメモリを持っていません.
通常のDBMSやファイルは第1の記憶媒体である、redisやmemcachedなどのメモリデータベースはデータをメモリに記憶する.(ここではそれらの持続化機能を捨てます)
ここではファイルキャッシュについて検討するが、記憶媒体をディスクに固定することが決定する.
3.キャッシュの管理:
これはキャッシュコアである難点であり、キャッシュの迅速な取得と記憶、期限切れのキャッシュ内容の自動クリーンアップを含む.
ここで、redisまたはmemcachedは、キャッシュデータを格納および取得するためのインタフェースを提供し、キャッシュの内容を自動的に管理するキャッシュ管理ツールとして理解することができる.
ファイルキャッシュについては、完全に自分で実現するので、ここのすべてのステップは私たちが自分で処理する必要があります.キャッシュ・コンテンツの管理には、次の2つの方法があります.
    (1). 簡易DBMS方式:MySQLのMyISAMエンジンと同様に、インデックスファイルとn個のデータファイルが与えられる.記憶:データファイルに書き込む、データインデックス情報をメモしてからインデックスファイルに書き込む.(少なくとも2回のディスク操作).取得:条件に基づいてインデックスファイルを検索、データインデックス情報を取得し、さらに迅速に位置決めしてデータを取得する.(少なくとも2回のディスク操作、インデックスファイルで使用するデータ構造を見る必要がある)利点:複数のデータが1つのデータファイルに格納、キャッシュファイルの数を効果的に制御でき、増加するデータ量、キャッシュの効率依存と実現プログラムに対応できる.欠点:データ量が大きいと、キャッシュの書き込みとクエリーの効率に影響し、効率を保証するために、このシステムは通常不活性な削除と更新を使用し、インデックスファイルとデータファイルが急速に膨張する.実現:phpにとって、bdb、gdbなどの拡張は良い選択である.
    (2). 結果ファイル方式:つまりキャッシュ結果ファイルである、このようなキャッシュの書き込みと取得は純粋なIO操作となり、性能はファイルシステムに依存する.ストレージ:指定ファイルに直接書き込む.取得:指定ファイルの内容を直接取得する.利点:データの直接取得、速度はbdb方式より速い.(一次ディスクインデックスクエリ時間と(size/(2^14)-size/(2^11))二次データ読み出し時間.欠点:ファイルの数は急速に膨張し、データが多いとファイルの検索速度に影響を与える.実装:後で方式の実装を与える.どちらの方式もキャッシュとすることができ、第1の方式はphp拡張をインストールする必要がある(拡張をインストールできればapcも良い選択である)、第2の方式はphpを使用して自分でよく実現することができる.(phpのIOの操作性能は悪くない)
ここでは、結果として1つのファイルを選択します.
4.増加するデータを考慮する:
memcachedまたはredisを用いると、tcp/IPに基づく単独で動作するサーバであり、「コンシステンシhashアルゴリズム」は、新たに増加する新しい機器によく適応し、無制限の拡張を実現し、増加するデータ量を解決することができる.ああ、ネットワークはもともと最高の分布式システムです.
上記のファイル組織方式を使用すると、これらのファイルをどのように管理すればよいのでしょうか.これも大きなデータを処理する方法である:パーティションは、ファイルにとってフォルダ別に格納される.
    (1). 垂直パーティション:フォルダ数nを固定し、n個のフォルダにファイルをハッシュする.最初は、1000個の小さなフォルダで100 Wのデータファイルを格納、各フォルダ内に100 W/1000=1000個のファイルを配置するなど、データ量を推定する必要がある.もちろんこれは理想的な情況の下で、事実のいかなるhash関数のすべて実際の中のこの効果を保証することができません.
    (2). 水平パーティション:各フォルダに格納データファイルの数を固定し、フォルダを増加させる.まず、キャッシュデータファイルの数を推定する100 W、サブフォルダごとに1000個のデータファイルが格納されていることを決定するなど、大まかなデータ量を決定する必要がある.すなわち、0-999のデータファイルは0というサブフォルダに格納、1000-199のデータファイルは1というフォルダに格納.このように...
    (3). 例えば、1つの文章リストページがあり、1級の分類が行われています.つまり、通常、このページにアクセスするには2つのパラメータが必要です.1).tidカテゴリId,2).pagenoは現在ページ番号を表示します.この場合、tidに基づいてフォルダを分割し、pagenoに基づいて水平パーティションを作成することで、より良い効果を得ることができます.すなわち、キャッシュ管理ツールは、開発者がパーティションを操作できるようにインタフェースを提供することが望ましい.実際の状況に基づいて、私たちは第3の方法を選んだ.
5.簡潔で効率的な実現:
    (1). インタフェースの決定:キャッシュ管理ツールには、書き込みキャッシュ:set(_key,_value);取得キャッシュ:get(_key,_value);
<?php
/**
 * dynamic content cache common interface .
*/
interface ICache {
    public function get( $_baseId, $_factor, $_time );
    public function set( $_baseId, $_factor, $_content );
}
?>

    (2). 上記の管理方式に従ってインタフェースを実現する.redisおよびmemcachedキャッシュクラスとの互換性を実現し、キャッシュシステムをファクトリモードに設計することができる.結局、開発にとって、キャッシュシステムはインタフェースが重要であり、redisとファイルキャッシュの違いは記憶媒体であり、それらは対外的に同じインタフェースを提供する必要がある.
    (3). phpに対してできるだけシステム関数を使って任務を完成することを助けて、キャッシュの書き込みと取得が簡単化することを確保します.
シンプルな実装:(製品で使用されるツールでもある)
<?php
/**
 * dynmaic content file cache class.
*/
class FileCache implements ICache {

    private $_length = 3000;
    public $_cache_dir = NULL;
    
    public function __construct( $_args ) {
        if ( $_args != NULL ) {
            if ( isset($_args['cache_dir']) )
                $this->_cache_dir = $_args['cache_dir'];
            if ( isset($_args['length']) )
                $this->_length = $_args['length'];
        }
    }
    
    private function getCacheFile($_baseId, $_factor) {
        $path = $this->_cache_dir.str_replace('.', '/', $_baseId);
        if ( $_factor != NULL ) {
            $path = $path.'/'.floor(($_factor / $this->_length));
            $_file = ($_factor % $this->_length).'.cache.html';
        } else {
            $_file = 'default.cache.html';
        }
        return ($path.'/'.$_file);
    }

    public function get( $_baseId, $_factor, $_time ) {
        $_cache_file = $this->getCacheFile($_baseId, $_factor);
        //echo $_cache_file,'<br />';
        
        if ( ! file_exists( $_cache_file ) ) return FALSE;
        if ( $_time < 0 ) return file_get_contents($_cache_file);
        if ( filemtime( $_cache_file ) + $_time < time() ) return FALSE;
        
        return file_get_contents($_cache_file);
        //return $_cache_file;
    }
    
    public function set( $_baseId, $_factor = NULL, $_content ) {
    
        $_cache_file = $this->getCacheFile($_baseId, $_factor);
        
        $path = dirname($_cache_file);
        //check and make the $path dir
        if ( ! file_exists( $path ) ) {
            $_dir = dirname( $path );
            $_names = array();
            do {
                if ( file_exists( $_dir ) ) break;
                $_names[] = basename($_dir);
                $_dir = dirname( $_dir );
            } while ( true );
            
            for ( $i = count($_names) - 1; $i >= 0; $i-- ) {
                $_dir = $_dir.'/'.$_names[$i];
                mkdir($_dir, 0x777);
            }
            mkdir($path, 0x777);
        }
        
        return file_put_contents($_cache_file, $_content);
    }
}
?>

私たちのアプリケーションシーンはページ全体の実行結果をキャッシュするので、キャッシュファイルの接尾辞には「.html」が使われています.セキュリティが必要なシステムでは、キャッシュファイルの接尾辞「.php」をできるだけ維持します.
ファイルリストページの呼び出し方法:
キャッシュ・クラスは$_に基づいています.keyの「.」自動パーティション化たとえば、次の例では$_が生成されます.cache_dir/article/list/$_tid/このフォルダは、このフォルダの下でpagenoレベルに基づいてパーティション化されます.
<?php
//$_tid      Id
//pageno        

$_cache = CacheFactory::create('file', array('cache_dir'=>'     '));
$_key = 'article.list.'.$_tid;
$_ret = $_cache->get($_key, $_pageno, 3600);
if ( $_ret != FALSE ) {
    echo $_ret;
    exit();
}


//    
$_cache->set($_key, $_pageno, 3600);
?>

ここで、キャッシュシステムをファクトリモードとして設計する、redisとmemcachedとを互換性があり、すなわち、ファイルからメモリキャッシュにキャッシュメディアを容易に移行することができる.CacheFactoryソース:
<?php
/**
 * dynamic content cache factory .
 *
 * @author chenxin <[email protected]>
*/
define('_CACHE_HOME_', dirname(__FILE__));
class CacheFactory {

    private static $_classes = NULL;
    
    public static function create( $_class, $_args = NULL ) {
        if ( self::$_classes == NULL ) {
            self::$_classes = array();
            //require the common interface
            require _CACHE_HOME_.'/ICache.class.php';
        }
        
        $_class = ucfirst( $_class ).'Cache';
        if ( ! isset( self::$_classes[$_class] ) ) {
            require _CACHE_HOME_.'/'.$_class.'.class.php';
            self::$_classes[$_class] = true;
        }
        
        //return the newly created object
        return new $_class($_args);
     }
}
?>