从代码结构优化redis缓存的方式, 还在为多人开发项目时混乱的redis key烦恼嘛?

我们目前数据层次按照性能来分    顶层 : 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烦恼了

猜你喜欢

转载自blog.csdn.net/u013807136/article/details/81156653