我们目前数据层次按照性能来分 顶层 : redis 中间层: es 底层: mysql
目前针对一些比较繁琐棘手的, 但是可以花点时间就能解决的问题, 就是在redis中的缓存问题
虽然目前业务量不是很大, 但是代码中会看到大家习惯性的为了提升性能而写出如下的代码
! 其中出现过一次redis崩溃事故
问题点: 1.为了不被缓存影响, 很多时候会在if的时候加上true,这样反复修改很麻烦,且出现过几次忘记改回来
2. 当线上出现事故是, 修复数据后依然没效果, 多数因为缓存导致,3. dao层数据处理不够统一化, 不同的开发者都根据自己的想法做缓存, 清理起来需要人工找到对应的key, 并手动执行del方法很是麻烦
4. 有好几个线上问题, 被之前写的自定义缓存key给坑惨了. 因为这种情况一旦出了问题, 定位并解决后发现没效果, 然后又花了很多时间来定位缓存位置.
之前代码的写法: /** * 获取用户地址所有信息 * @param $addressId * @return $this|mixed */ public function getAllList($type) { $ckey = self::cache()->genKey(self::CACHE_KEY_INFO_ALL, $type); $arrRet = self::cache()->forceGet($ckey); if ($arrRet === false) { $arrRet = self::find()->where('type', $type)->asArray()->all(); self::cache()->forceSet($ckey, $arrRet); } return $arrRet; }
♦ 解决方案:1. 针对后面新加的dao层代码, 尽量做到数据统一,更换key前缀的形成方式,为方法名
2. ORM本来就是那一个比较好用的封装方式了, 其中的一些的before after 用起来,把缓存统一就能解决很多问题
其实这里也只是用了ORM的用了所有dao层类继承的一个基础类, 在父类中统一了可以的set和get
(缓存的key现在根据类名, 方法名为前缀 特殊id为后缀, 不需要在手动设置key名 注: 针对已经写完的上线代码, 没法统一起来成本很高,暂不考虑)
新方案的写法:
使用出直接修改为:
$this->cacheOn(__FUNCTION__, $type); 即可,其他部分均已在orm中实现
例子:
/**
* 获取用户地址所有信息
* @param $addressId
* @return $this|mixed
*/
public function getAllList($type)
{
$this->cacheOn(__FUNCTION__, $type); //__FUNCTION__当前方法常量
return self::find()->where('type', $type)->asArray()->all();
}
实现详解:
1. 在cacheOn中把, 通过没文件统一控制缓存
2. 通过 $this->needCache控制缓存不错在时set缓存
3. 在$this->_afterFind() 中如果开启缓存,则把数据返回写入到$this->needCache所存储的key中
4. 通过del($key."*")的方式一次性更新本类的所有缓存, set的时间复杂度从O(n) 提升到O(xn) x为常量, 基本不影向性能,
多了一个keys搜索操作, 在超大redis数据中, keys方法影响性能, 目前也不考虑
实际代码:
/**
* 统一dao层开启缓存方式, 前提是配置文件['debug']['isCache']为true
* 注意: 只能在查询的上一步,缓存开启,不允许嵌套缓存
* @param $function string dao层调用的常量__FUNCTION__必须填固定常量
* @param $key
* @return bool
* @throws Yaf_Exception
*/
protected function cacheOn($function, $key)
{
$config = Utils_Common::getConfig(); //缓存配置
if (!isset($config['debug']['isCache'])) {
throw new Yaf_Exception('isCache不能为空', -10000);
}
$isCache = $config['debug']['isCache'];
if ($isCache === true) {
//key由class function 和特殊id组成
$key = self::cache()->getKey(__CLASS__.$function, $key);
if ($ret = self::cache()->get($key) !== false) {
return $ret;
} else {
$this->needCache = $key; //表示我是需要缓存的
}
}
}
/**
* 查询之后执行
* @param $models
*/
protected function _afterFind($models)
{
if (!empty($this->needCache)) {
self::cache()->forceSet($this->needCache, $models);
}
}
缓存清空问题:
直接使用del key*的方式清空本类的缓存
/**
* 更新之前执行
* @param $attributes
*/
protected function _beforeUpdate($attributes)
{
//redis的set次数时间复杂度从O(n)提升到O(xn),不会影响性能 x 常量可忽略, n为更新次数, 这种方案不适合秒杀
//删除该类的所有redis缓存
$keys = $this->cache()->keys(__CLASS__.'*');
$this->cache()->del($keys);
}
!!!!!!!!: 这里可能会看着奇怪, redis set的时候没有设置过期时间, 其实只是我们封装了一层, forsetSet设置了默认过期时间3600s
!!!!!!!!: 这种设计方案是, 当一个dao层的任何一个数据有更新, 就要删除这个Dao类名前缀的所有redis, key 这种方案是有缺陷的,
在秒杀环境下就会出现直接打到mysql的情况, 当然解决方案也很简单, 就是这里统一封装的时候, 在强制规定上指定的id即可, 比如userId, couponId等方式.
性能问题的解决方案: 我综合当前系统得出的解决方案中, keys造成了性能问题, 其实只要再做一个简单的缓存就能迎刃而解, 用redis的sadd集合把改dao类名前缀的key存储起来, 相当于把keys做了一个索引, 见代码:
self::cache()->sAdd(__CLASS__, self::$needCache); //用户索引存了什么key
$keys = $this->cache()->sGetMembers(__CLASS__);
替代
$keys = $this->cache()->keys(__CLASS__.'*');
/**
* 查询之后执行
* @param $models
*/
protected function _afterFind($models)
{
if (!empty(self::$needCache)) {
self::cache()->sAdd(__CLASS__, self::$needCache);
self::cache()->set(self::$needCache, $models);
self::$needCache = '';
}
}
/**
* 更新之前执行
* @param $attributes
*/
protected function _beforeUpdate($attributes)
{
//redis的set次数时间复杂度从O(n)提升到O(xn),不会影响性能 x 常量可忽略, n为数据的更新次数
//删除该类的所有redis缓存
$keys = $this->cache()->sGetMembers(__CLASS__);
// $keys = $this->cache()->keys(__CLASS__.'*');
$this->cache()->del($keys);
}
这样这个点上的问题也能解决了
东西不多, 主要从一个封装层面做了一下redis缓存key的统一化, 在现有基础思想上, 还可以做很多优化,
简单的几步, 就不用再为一些找不到的redis key烦恼了