基于PHP的一种Cache回调与自动触发技术_PHP教程
基于PHP的一种Cache回调与自动触发技术
背景
在PHP中使用Memcache或者Redis时,我们一般都会对Memcache和Redis封装一下,单独完成写一个Cache类,作为Memcache活着Redis的代理,且一般为单例模式。在业务代码中,使用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;
基本流程为
第一步,先组装查询key,到Cache查询Value,如果存在,继续处理,进入第三步;如果不存在,进入第二步
第二步,根据请求,到DB中,查询相关数据,如果数据存在,把数据放到Cache中
第三步,处理cache或者db中返回的数据
问题
上述流程基本上会出现在每次调用Cache的部分,先cache查询,没有的话调用DB或者第三方接口,获取数据,再次存入Cache,继续数据处理。多次调用,既是一种问题,应该把这种查询方式封装到更底层的方法内。而不是每次重复这样的逻辑,除了封装的问题外,还有其他问题,我们统一列举下
第一:从设计角度来说 重复代码,需要更底层逻辑封装。
第二:key的组装,麻烦繁琐,实际情况,可能会把各种参数组装进去,维护的时候,不敢修改。
第三:设置的expire超时时间,会分散在各处逻辑代码中,最终很难统计Cache缓存时间的情况。
第四:由于要把cache->store方法放到调用db之后执行,如果db后,还有其他逻辑处理,有可能会忘掉把数据放入cache,导致数据。
第五:在高并发系统中,cache失效那一刻,会有大量请求直接穿透到后方,导致DB或者第三方接口压力陡升,响应变慢,进一步影响系统稳定性,这一现象为“Dogpile”。
以上问题中,最简单的为2,3种,对于expire超时时间分散的问题,我们可以通过统一配置文件来解决,比如我们可以创建这样的一个配置文件。
“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组装成唯一的入库key,组装后的key,大概是这样site_top_feeds_site_id=12&boutique=1&offset=0&rows=20&uid=&tag_id=0&type=2通过这种方式,我们避免工人自己组装key,从而杜绝第二种问题,同时在这个配置文件中,我们也设置了timeout,这样调用store时,我们可以直接从配置文件中读取,从而避免第三个问题。经过如上修改后,我们的cache方法,也做了适当的调整,调用示例如下。
$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'); }
通过以上方案,我没看到,timeout超时时间没有了,key的组装也没有了,对于外层调用是透明的了。但是我们通过配置文件可以知道site_top_feeds的timeout是多少,通过封装的算法,知道组装的key是什么样的。
这种方式,并没有解决第一和第四的问题,封装性;要想完成封装性,第一件事情要做的就是回调函数,作为校本语言,PHP并没有完善的函数指针概念,当然要想执行一个函数其实也不需要指针。PHP支持回调函数的方法有两种call_user_func,call_user_func_array。
但是,我做了两个例子,发现上述方法,执行效率比原生方法,相差很多
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倍多,具体封装后的代码
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); }
解决方案
好了,关键核心代码如下
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的访问次数,提高性能。

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











자율주행 궤적 예측은 차량의 주행 과정에서 발생하는 다양한 데이터를 분석하여 차량의 향후 주행 궤적을 예측하는 것을 의미합니다. 자율주행의 핵심 모듈인 궤도 예측의 품질은 후속 계획 제어에 매우 중요합니다. 궤적 예측 작업은 풍부한 기술 스택을 보유하고 있으며 자율 주행 동적/정적 인식, 고정밀 지도, 차선, 신경망 아키텍처(CNN&GNN&Transformer) 기술 등에 대한 익숙함이 필요합니다. 시작하기가 매우 어렵습니다! 많은 팬들은 가능한 한 빨리 궤도 예측을 시작하여 함정을 피하기를 희망합니다. 오늘은 궤도 예측을 위한 몇 가지 일반적인 문제와 입문 학습 방법을 살펴보겠습니다. 관련 지식 입문 1. 미리보기 논문이 순서대로 되어 있나요? A: 먼저 설문조사를 보세요, p

StableDiffusion3의 논문이 드디어 나왔습니다! 이 모델은 2주 전에 출시되었으며 Sora와 동일한 DiT(DiffusionTransformer) 아키텍처를 사용합니다. 출시되자마자 큰 화제를 불러일으켰습니다. 이전 버전과 비교하여 StableDiffusion3에서 생성된 이미지의 품질이 크게 향상되었습니다. 이제 다중 테마 프롬프트를 지원하고 텍스트 쓰기 효과도 향상되었으며 더 이상 잘못된 문자가 표시되지 않습니다. StabilityAI는 StableDiffusion3이 800M에서 8B 범위의 매개변수 크기를 가진 일련의 모델임을 지적했습니다. 이 매개변수 범위는 모델이 많은 휴대용 장치에서 직접 실행될 수 있어 AI 사용이 크게 줄어든다는 것을 의미합니다.

첫 번째 파일럿 및 주요 기사에서는 주로 자율 주행 기술에서 일반적으로 사용되는 여러 좌표계를 소개하고 이들 간의 상관 관계 및 변환을 완료하고 최종적으로 통합 환경 모델을 구축하는 방법을 소개합니다. 여기서 초점은 차량에서 카메라 강체로의 변환(외부 매개변수), 카메라에서 이미지로의 변환(내부 매개변수), 이미지에서 픽셀 단위로의 변환을 이해하는 것입니다. 3D에서 2D로의 변환에는 해당 왜곡, 변환 등이 포함됩니다. 요점: 차량 좌표계와 카메라 본체 좌표계를 다시 작성해야 합니다. 평면 좌표계와 픽셀 좌표계 난이도: 이미지 평면에서 왜곡 제거와 왜곡 추가를 모두 고려해야 합니다. 2. 소개 좌표계에는 픽셀 평면 좌표계(u, v), 이미지 좌표계(x, y), 카메라 좌표계(), 월드 좌표계() 등 총 4가지 비전 시스템이 있습니다. 각 좌표계 사이에는 관계가 있으며,

본 논문에서는 자율 주행에서 다양한 시야각(예: 원근 및 조감도)에서 객체를 정확하게 감지하는 문제, 특히 원근(PV) 공간에서 조감(BEV) 공간으로 기능을 효과적으로 변환하는 방법을 탐구합니다. VT(Visual Transformation) 모듈을 통해 구현됩니다. 기존 방법은 크게 2D에서 3D로, 3D에서 2D로 변환하는 두 가지 전략으로 나뉩니다. 2D에서 3D로의 방법은 깊이 확률을 예측하여 조밀한 2D 특징을 개선하지만, 특히 먼 영역에서는 깊이 예측의 본질적인 불확실성으로 인해 부정확성이 발생할 수 있습니다. 3D에서 2D로의 방법은 일반적으로 3D 쿼리를 사용하여 2D 기능을 샘플링하고 Transformer를 통해 3D와 2D 기능 간의 대응에 대한 주의 가중치를 학습하므로 계산 및 배포 시간이 늘어납니다.

저자 개인 생각 중 일부 자율주행 분야에서는 BEV 기반의 하위 작업/End-to-End 솔루션 개발로 인해 고품질의 다시점 훈련 데이터와 그에 따른 시뮬레이션 장면 구축이 점점 더 중요해지고 있습니다. 현재 작업의 문제점에 대응하여 "고품질"은 세 가지 측면으로 분리될 수 있습니다. 다양한 차원의 롱테일 시나리오(예: 장애물 데이터의 근거리 차량 및 자동차 절단 과정의 정확한 방향 각도) 곡률이 다른 곡선이나 경사로/병합/병합 등 캡처하기 어려운 차선 데이터. 이는 종종 비용이 많이 드는 대량의 데이터 수집과 복잡한 데이터 마이닝 전략에 의존합니다. 3D 진정한 가치 - 매우 일관된 이미지: 현재 BEV 데이터 수집은 센서 설치/보정, 고정밀 지도 및 재구성 알고리즘 자체의 오류에 의해 영향을 받는 경우가 많습니다. 이것이 나를 이끌었다

갑자기 발견한 19년 된 논문 GSLAM: A General SLAM Framework and Benchmark 오픈소스 코드: https://github.com/zdzhaoyong/GSLAM 전문으로 직접 가서 이 작품의 퀄리티를 느껴보세요~1 Abstract SLAM technology 최근 많은 성공을 거두었으며 많은 첨단 기술 기업의 관심을 끌었습니다. 그러나 기존 또는 신흥 알고리즘에 대한 인터페이스를 사용하여 속도, 견고성 및 이식성에 대한 벤치마크를 효과적으로 수행하는 방법은 여전히 문제로 남아 있습니다. 본 논문에서는 평가 기능을 제공할 뿐만 아니라 연구자에게 자체 SLAM 시스템을 신속하게 개발할 수 있는 유용한 방법을 제공하는 GSLAM이라는 새로운 SLAM 플랫폼을 제안합니다.

이 네모난 남자는 눈앞에 있는 '불청객'의 정체를 고민하며 미간을 찌푸리고 있다는 점에 주목해주세요. 알고 보니 그녀는 위험한 상황에 처해 있었고, 이를 깨닫자마자 문제를 해결하기 위한 전략을 찾기 위해 재빨리 정신적 탐색을 시작했습니다. 결국 그녀는 현장을 떠나 가능한 한 빨리 도움을 구하고 즉각적인 조치를 취하기로 결정했습니다. 동시에 반대편에 있는 사람도 그녀와 같은 생각을 하고 있었는데... <마인크래프트>에도 모든 캐릭터가 인공지능에 의해 조종되는 장면이 있었다. 예를 들어 앞서 언급한 소녀는 17세지만 똑똑하고 용감한 택배기사입니다. 그들은 마인크래프트를 배경으로 한 이 작은 마을에서 인간처럼 기억하고 생각하며 살아갈 수 있는 능력을 가지고 있습니다. 그들을 움직이는 것은 아주 새로운 것입니다.

위에 작성됨 & 저자의 개인적인 이해는 이미지 기반 3D 재구성은 입력 이미지 세트에서 객체나 장면의 3D 모양을 추론하는 어려운 작업이라는 것입니다. 학습 기반 방법은 3차원 형상을 직접 추정할 수 있는 능력으로 주목을 받았습니다. 이 리뷰 논문은 새로운, 보이지 않는 뷰 생성을 포함한 최첨단 3D 재구성 기술에 중점을 두고 있습니다. 입력 유형, 모델 구조, 출력 표현 및 훈련 전략을 포함하여 가우스 스플래시 방법의 최근 개발에 대한 개요가 제공됩니다. 해결되지 않은 과제와 앞으로의 방향에 대해서도 논의한다. 해당 분야의 급속한 발전과 3D 재구성 방법을 향상할 수 있는 수많은 기회를 고려할 때 알고리즘을 철저히 조사하는 것이 중요해 보입니다. 따라서 이 연구는 가우스 산란의 최근 발전에 대한 포괄적인 개요를 제공합니다. (엄지손가락을 위로 스와이프하세요.
