ホームページ  >  記事  >  バックエンド開発  >  PHP_PHP チュートリアルに基づくキャッシュ コールバックと自動トリガー テクノロジ

PHP_PHP チュートリアルに基づくキャッシュ コールバックと自動トリガー テクノロジ

WBOY
WBOYオリジナル
2016-07-13 10:12:03811ブラウズ

PHPに基づくキャッシュコールバックと自動トリガーテクノロジー

背景

PHP で Memcache または Redis を使用する場合、通常は Memcache と Redis をカプセル化し、Memcache または Redis のプロキシとして Cache クラスを個別に、通常はシングルトン モードで作成します。ビジネスコードでCacheクラスを使用する場合の基本的な操作のサンプルコードは以下の通りです

// cache 的 key
$key = 'this is key';
$expire = 60;// 超时时间

// cache 的实例
$cache = Wk_Cache::instance();
$data = $cache->fetch($key);

// 判断data
if(empty($data)){
    // 如果为空,调用db方法
    $db = new Wk_DB();
    $data = $db->getXXX();
    $cache->store($key, $data, $expire);
}
// 处理$data相关数据
return $data;

基本的な処理は
最初のステップはクエリキーを組み立ててキャッシュ内の値をクエリすることです存在する場合は、処理を続行して 3 番目のステップに進みます。存在しない場合は、2 番目のステップに進みます。 2 番目のステップは、リクエストに従って DB に関連するデータをクエリすることです。データが存在する場合は、データを配置します。キャッシュ内で
3番目のステップは、キャッシュまたはDBデータで返されたデータを処理することです

問題

上記のプロセスは、基本的に毎回キャッシュが呼び出される部分に表示されます。そうでない場合は、DBを呼び出します。またはサードパーティのインターフェイスを使用してデータを取得し、再度キャッシュに保存して、データ処理を続行します。複数の呼び出しが問題となるため、このクエリ メソッドは下位レベルのメソッドにカプセル化する必要があります。このようなロジックを毎回繰り返すのではなく、カプセル化の問題に加えて、他の問題もまとめてリストしてみましょう

まず、設計の観点から、コードを繰り返すには低レベルのロジックのカプセル化が必要です。

2 番目: キーの組み立ては面倒で面倒です。実際には、メンテナンス中にさまざまなパラメーターを組み立てることはできません。
3 番目: 設定された有効期限タイムアウトはさまざまな論理コードに分散され、キャッシュ キャッシュ時間の計算が困難になります。
4番目: dbを呼び出した後にcache->storeメソッドを実行する必要があるため、dbの後に他の論理プロセスがある場合、データをキャッシュに入れるのを忘れてデータが失われる可能性があります。
5 番目: 同時実行性の高いシステムでは、キャッシュに障害が発生すると、多数のリクエストが直接後方に浸透し、DB またはサードパーティのインターフェイスに対する圧力が急激に上昇し、応答が遅くなり、さらに影響が及びます。システムの安定性を低下させる現象が「ドッグパイル」です。

上記の問題のうち、最も単純なものは 2 と 3 です。散在する有効期限タイムアウトの問題については、たとえば、このような設定ファイルを作成することで解決できます。

“test"=>array( // namespace,方便分组
             "keys"=> array(
                 “good”=>array(		// 定义的key,此key非最终入cache的key,入key需要和params组装成唯一的key
                     "timeout"=>600,	// 此处定义超时时间
                     "params"=>array("epid"=>1,"num"=>1),	// 通过这种方法,描述需要传递参数,用于组装最终入cache的key
                     "desc"=>"描述"
                     ), 
                "top_test"=>array(	// 定义的key,此key非最终入cache的key,入key需要和params组装成唯一的key
                     "timeout"=>60,	// 此处定义超时时间
                     "ttl"=>10,	// 自动触发时间
                     "params"=>array('site_id'=>1,'boutique'=>1,'offset'=>1,'rows'=> 1,'uid'=>1,'tag_id'=>1,'type'=>1),	// 通过这种方法,描述需要传递参数,用于组装最终入cache的key
                     "desc"=>"描述",
                     "author"=>"ugg",
                     ), 

)
)


上に示したように、アルゴリズムを通じて site_top_feeds と params を一意のストレージ キーに組み立てることができます。組み立てられたキーはおそらく次の site_top_feeds_site_id=12&boutique=1&offset=0&rows=20&uid=&tag_id= 0&type=2 になります。このようにして、ワーカーが自分でキーを組み立てることを回避し、それによって 2 番目の問題を解消します。同時に、この構成ファイルではタイムアウトも設定し、ストアを呼び出すときに構成ファイルから直接読み取ることができるようにします。これにより、3 番目の問題が回避されます。上記の変更後、キャッシュ メソッドも適切に調整されています。呼び出し例は次のとおりです。

$siteid = 121;
$seminal = 1;
$tag_id = 12;
$tag_id = 22;

$data =  fetch(‘site_top_feeds’,array('site_id'=>$siteid,'boutique'=>$seminal, 'offset'=>"0", 'rows' => "20", 'uid' =>null,’tag_id’=>$tag_id,’type'=>$type),'feed');
if(empty($data)){
// db相关操作
$db = new Wk_DB();
    $data = $db->getTopFeeds($site_id,$seminal,0,20,null,$tag_id,$type);
//  $data数据其他处理逻辑 这里
……

$cache->store(‘site_top_feeds’,$data,array(‘site_id'=>$siteid,'boutique'=>$seminal, 'offset'=>"0", 'rows' => "20", 'uid' =>null,’tag_id’=>$tag_id,’type'=>$type),'feed');
}


上記の解決策では、タイムアウトタイムアウトがなくなったこと、キーアセンブリがなくなったこと、そして外部呼び出しに対して透過的であることが分かりませんでした。ただし、構成ファイルを通じて site_top_feeds のタイムアウトを知ることができ、カプセル化されたアルゴリズムを通じて組み立てられたキーがどのようになるかを知ることができます。

この方法では、1 番目と 4 番目の問題であるカプセル化は解決されません。カプセル化を実現するために最初に行うことは、学校ベースの言語である PHP には関数ポインターの完全な概念がありません。実際には、関数を実行するためにポインターは必要ありません。 PHP は、call_user_func と call_user_func_array の 2 つの方法でコールバック関数をサポートします。
ただし、2 つの例を作成したところ、上記のメソッドの実行効率はネイティブ メソッドの実行効率とは大きく異なることがわかりました

native:0.0097959041595459s
call_user_func:0.028249025344849s
call_user_func_array:0.046605110168457s

サンプル コードは次のとおりです:

$s = microtime(true);
for($i=0; $i< 10000 ; ++$i){
    $a = new a();
    $data = $a->aaa($array, $array, $array);
    $data = a::bbb($array, $array, $array);
}
$e = microtime(true);
echo "native:".($e-$s)."s\n";

$s = microtime(true);
for($i=0; $i< 10000 ; ++$i){
    $a = new a();
    $data = call_user_func(array($a,'aaa'),$array,$array,$array);
    $data = call_user_func(array('a','bbb'),$array,$array,$array);
}
$e = microtime(true);
echo "call_user_func:".($e-$s)."s\n";

$s = microtime(true);
for($i=0; $i< 10000 ; ++$i){
    $a = new a();
    $data = call_user_func_array(array($a,'aaa'),array(&$array,&$array,&$array));
    $data = call_user_func_array(array('a','bbb'),array(&$array,&$array,&$array));
}
$e = microtime(true);
echo “call_user_func_array:".($e-$s)."s\n";

PHP では、オブジェクトとメソッドを実際に呼び出すのは非常に簡単です。たとえば、上記の例

$a = new a();
$data = $a->aaa($array, $array, $array);
$obj = $a;
$func = ‘aaa’;
$params = array($array,$array,$array);
$obj->$func($params[0],$params[1],$params[2]);	// 通过这种方式可以直接执行

このメソッドの実行パフォーマンスはどうですか? 比較テストの結果、

native:0.0092940330505371s
call_user_func:0.028635025024414s
call_user_func_array:0.048038959503174s
my_callback:0.11308288574219s

には多数のメソッド戦略検証が追加されていることがわかりました。パフォーマンスの低下は比較的低く、消費時間はネイティブ メソッドのわずか 1.25 倍です。 call_user_func の約 3 倍、call_user_func_array の 5 倍以上です。 このメソッドを使用する前に、 create_functionを使用して匿名関数を作成し、関数コールバックを実行することを検討しましたが、テストした後、create_functionだけがグローバル関数を作成できましたが、クラス関数とオブジェクト関数を作成できなかったので、あきらめました。


上記の準備が完了したら、コールバックメカニズムを使用して、ビジネスコードを再度呼び出すことができます

….
// 相关变量赋值
$db = new Wk_DB();
$callback['obj'] = $db;
            $callback['func'] = 'getTopFeeds';
            $callback['params'] = array('site_id'=>$siteid,'boutique'=>$seminal, 'offset'=>"0", 'rows' => "20", 'uid' =>null,'tag_id'=>$tag_id,'type'=>$type);

            $top_feed_list = $cache->smart_fetch('site_top_feeds',$callback,'feed');

使用以上方法实现对cache调用的封装,同时保证性能的高效,从而解决第一和第四个问题。

至此已经完成前四个问题,从而实现Cache的封装,并有效的避免了上面提到的第二,第三,第四个问题。但是对于第五个问题,dogpile问题,并没有解决,针对这种问题,最好的方式是在cache即将失效前,有一个进程主动触发db操作,获取DB数据放入Cache中,而其他进程正常从Cache中获取数据(因为此时cache并未失效);好在有Redis缓存,我们可以使用Redis的两个特性很好解决这个问题,先介绍下这两个接口
TTL方法:以秒为单位,返回给定 key 的剩余生存时间 (TTL, time to live),当 key 不存在时,返回 -2 。当 key 存在但没有设置剩余生存时间时,返回 -1 。否则,以秒为单位,返回 key 的剩余生存时间。很明显,通过这个方法,我们很容易知道key的还剩下的生存时间,通过这个方法,可以在key过期前做点事情,但是光有这个方法还不行,我们需要确保只有进程执行,而不是所有的进程都做,正好用到下面这个方法。
SETNX方法:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则SETNX 不做任何动作。SETNX 是『SET if Not eXists』(如果不存在,则 SET) 的简写。返回值:设置成功,返回 1 。设置失败,返回 0 。通过这个方法,模拟分布式加锁,保证只有一个进程做执行,而其他的进程正常处理。结合以上Redis方法的特性,解决第五种的问题的,实例代码。

…
// 变量初始化
$key = “this is key”;
$expiration = 600; 
$recalculate_at = 100;
$lock_length = 20;
$data = $cache->fetch($key); 
$ttl = $cache->redis->ttl($key); 
if($recalculate_at>=$ttl&&$r->setnx("lock:".$key,true)){ 
$r->expire(“lock:”.$key, $lock_length);
$db = new Wk_DB();
  $data = $db->getXXX();
  $cache->store($key, $expiration, $value);
}

解决方案

好了,关键核心代码如下
1:function回调部分代码

public static function callback($callback){
        // 安全检查
        if(!isset($callback['obj']) || !isset($callback['func'])
            || !isset($callback['params']) || !is_array($callback['params'])){
            throw new Exception("CallBack Array Error");
        }   
        // 利用反射,判断对象和函数是否存在
        $obj = $callback['obj'];
        $func = $callback['func'];
        $params = $callback['params'];
        // 方法判断        
        $method = new ReflectionMethod($obj,$func);
        if(!$method){
            throw new Exception("CallBack Obj Not Find func");
        }   

        // 方法属性判断
        if (!($method->isPublic() || $method->isStatic())) {
            throw new Exception("CallBack Obj func Error");
        }   

        // 参数个数判断(不进行逐项检测)
        $paramsNum = $method->getNumberOfParameters();
        if($paramsNum < count($params)){
            throw new Exception("CallBack Obj Params Error");
        }   

        // 6个参数以内,逐个调用,超过6个,直接调用call_user_func_array
        $result = false;
        // 判断静态类方法
        if(!is_object($obj) && $method->isStatic()){
            switch(count($params)){
                case 0: $result = $obj::{$func}();break;
		case 1: $result = $obj::{$func}($params[0]);break;
                case 2: $result = $obj::{$func}($params[0],$params[1]);break;
                case 3: $result = $obj::{$func}($params[0],$params[1],$params[2]);break;
                case 4: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3]);break;
                case 5: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3],$params[4]);break;
                case 6: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5]);break;
                case 7: $result = $obj::{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5],$params[6]);break;
                default: $result = call_user_func_array(array($obj, $func), $params);  break;
            }
        }else{
            switch(count($params)){
                case 0: $result = $obj->{$func}();break;
                case 1: $result = $obj->{$func}($params[0]);break;
                case 2: $result = $obj->{$func}($params[0],$params[1]);break;
                case 3: $result = $obj->{$func}($params[0],$params[1],$params[2]);break;
                case 4: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3]);break;
                case 5: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4]);break;
                case 6: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5]);break;
                case 7: $result = $obj->{$func}($params[0],$params[1],$params[2],$params[3],$params[4],$params[5],$params[6]);break;
                default: $result = call_user_func_array(array($obj, $func), $params);  break;
            }
        }

2:自动触发回调机制

public function smart_fetch($key,$callback,$namespace="wk") {
	key = $prefix.$key.$suffix;
        $result = $this->_redis->get($key);

        $bttl = false;
        // ttl状态判断(注意冷启动)
        if(!empty($ttl)){
            // 获得过期时间
            $rttl = $this->_redis->ttl($key);
            if($rttl > 0 && $ttl >= $rttl &&
                $this->_redis->setnx("lock".$key,true)){
                // 设置超时时间(超时时间3秒)
                $this->_redis->expire("lock".$key,3);
                $bttl = true;
            }
        }
	// 如何返回值不存在,调用回调函数,获取数值,并保持数据库
        if($bttl || !$result || (isset($CONFIG['FLUSH']) && !empty($CONFIG['FLUSH']))){
            // 重新调整参数
            $callbackparams = array();
            foreach($params as $k=>$value){
                $callbackparams[] = $value;
            }
            $callback['params'] = $callbackparams;
            $result = Wk_Common::callback($callback);
            $expire = $key_config["timeout"];
            // 存储数据
            $status = $this->_redis->setex($key, $expire, $result);
            $result=$this->_redis->get($key);
        }

        // 删除锁
        if($bttl){
            $this->_redis->delete("lock".$key);
        }
        return $result;
    }

至此,我们使用脚本语言特性,通过user_call_func_array方法补齐所有函数回调机制,从而实现对Cache的封装,通过配置文件定义组装key的规则和每个key的超时时间,再通过Redis的ttl和setnx特性,保证只有一个进程执行DB操作,从而很好避免dogpile问题,实现cache自动触发,保证cache持续存在数据,并且有效减少DB的访问次数,提高性能。

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/925224.htmlTechArticle基于PHP的一种Cache回调与自动触发技术 背景 在PHP中使用Memcache或者Redis时,我们一般都会对Memcache和Redis封装一下,单独完成写一个Cache类,...
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。