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

1 篇文章 0 订阅
1 篇文章 0 订阅

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis缓存是一种基于内存的高性能键值存储数据库。它常被用作缓存服务器,可以将常用的数据缓存在内存,以提高应用程序的响应速度。 优点: 1. 快速读取:与传统的磁盘数据库相比,Redis缓存可以更快地读取数据。 2. 高并发:Redis缓存的单线程模型能够避免并发问题,同支持高并发访问。 3. 数据结构丰富:Redis缓存支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等,方便开发人员使用。 4. 分布式:Redis缓存可以实现分布式缓存,提高了系统的扩展性和容错性。 缺点: 1. 内存限制:Redis缓存存储的数据量受限于服务器的内存大小。 2. 数据一致性:Redis缓存的数据可能会因为故障等原因丢失,需要进行备份和恢复操作。 3. 高并发写入:当Redis缓存的数据需要频繁更新,可能会导致性能下降。 Redis缓存支持多种数据结构,如字符串、哈希表、列表、集合、有序集合等。其,字符串适用于缓存简单的值或对象,哈希表适用于缓存复杂的对象,列表适用于缓存队列等数据结构,集合适用于缓存无序的元素集合,有序集合适用于缓存有序的元素集合。 Redis缓存的分布式实现可以通过一致性哈希算法等方式来实现。一致性哈希算法可以使得数据在多个节点之间均匀分布,提高系统的性能和可靠性。 为了保证Redis缓存的可靠性和数据一致性,可以使用持久化方式来将数据写入到磁盘,以防止数据丢失。同,可以设置主从复制,将数据复制到多个节点,提高系统的可靠性。 Redis缓存的过期策略有两种:定过期和惰性过期。定过期是指设置一个过期间,在这个间之后数据会被自动删除;惰性过期是指在访问数据检查它是否过期,如果过期则进行删除。可以通过设置过期间和过期策略来控制Redis缓存数据的有效性。 Redis缓存的持久化方式有两种:RDB和AOF。RDB将内存的数据周期性地写入到磁盘,适用于需要快速备份和恢复数据的场景;AOF则将Redis缓存的写操作记录到文件,适用于需要保证数据一致性和可靠性的场景。 为了优化Redis缓存的性能,可以采用以下方法: 1. 合理使用数据结构,选择适合的数据类型和算法。 2. 设置合理的过期间和过期策略,避免数据的过期和无效。 3. 使用分布式缓存,将数据分散在多个节点,提高系统的性能和可靠性。 4. 使用连接池和异步IO等技术,避免因连接和IO造成的性能瓶颈。 为了保证Redis缓存与数据库的一致性,可以使用缓存更新策略。当数据库的数据发生变化,可以通过订阅数据库更新事件的方式,将更新的数据同步到Redis缓存,以保证数据的一致性。 为了实现Redis缓存的高可用性,可以使用主从复制和哨兵模式。主从复制可以将数据复制到多个节点,提高系统的容错性;哨兵模式则可以监控Redis缓存的状态,当主节点出现故障,自动选择新的主节点,保证系统的高可用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值