前段时间接到个需求,要求现有的数据做到实时更新。目前相关接口都是等待缓存过期才能查到最新的数据,如果想做到实时更新,就要在生成数据后去触发删除缓存数据
目前的项目使用了TP5框架,开始想的是使用框架自带的Cache类的Tag进行缓存的标记与删除,看过源码后发现TP5的tag实现并不是很理想,其把缓存key使用逗号分隔存在一个tag中,并发场景中,可能会出现tag中的key丢失的情况,最后还是计划通过给缓存加前缀,然后通过原生keys匹配到具体的缓存key列表,然后使用del方法删除缓存。
为了在最小程度上修改代码,我通过自定义中间件判断path并确定是否需要打上标签的数据,然后判断Request的cacheTag属性进行标记处理,中间件的实现如下:
<?php
namespace app\common\middleware;
use think\Request;
class CacheTag
{
protected $IndexApiList = [
'home/index/index',
'home/inside/testindex',
'home/inside/nnindex'
];
/**
* 缓存标记处理:对于指定接口进行目录标记,方便统一清除缓存
* @access public
* @return mixed
**/
public function handle()
{
$request = Request::instance();
$path = strtolower($request->module() . '/' . $request->controller() . '/' . $request->action());
switch ($path) {
case in_array($path, $this->IndexApiList) :
$request->cacheTag = 'tagIndex';
return;
default :
$request->cacheTag = false;
return;
}
}
}
上面实现了对缓存的标记,删除缓存代码就不贴出来了。
测试了几遍没什么问题就发布到线上了,谁曾想线上出现了问题,有几个接口返回接口是空数组,并且空结果被缓存了,导致前台项目长时间获取不到数据。开始以为是删除缓存的问题,排查了一番之后并不是,于是又去翻了之前的代码,真的发现了问题,由于之前的缓存策略实现是model层、Controller层的双层缓存策略,同时缓存获取也很不规范,竟然获取了两次缓存(当然这不规范的代码都是前人留下的,这种操作着实很迷)
$cacheName = "home_index_index";
$cacheClass = new \think\Cache();
if($cacheClass->get($cacheName)){
$res = $cacheClass->get($cacheName);
}else{
$res = $this->where($param)->select()->toArray();
$cacheClass->set($cacheName,$res,22000);
}
return $res;
到这里我基本断定是因为获取两次缓存的原因了。设想一下生成空结果缓存的流程:
接口存在访问量,这时的model层在第一次获取到缓存的时候缓存是有的,这时生成指数任务触发删除缓存操作,那么model层第二次获取接口缓存的时候就返回的是空结果了。那么整个controller层执行完毕之后就会有部分从model层获取的数据为空的数据被缓存了下来
至此,问题找到了。