基于PHP的一种Cache回调与自动触发技术
$s = microtime(true); for($i=0; $iaaa($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
在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倍多,具体封装后的代码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;
}
在使用这种方法之前,考虑过使用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);
}