前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >高并发架构技术|缓存失效、缓存穿透问题 PHP 代码解决

高并发架构技术|缓存失效、缓存穿透问题 PHP 代码解决

作者头像
猿哥
发布2019-07-25 23:15:13
9350
发布2019-07-25 23:15:13
举报
文章被收录于专栏:Web技术布道师Web技术布道师

问题描述

缓存失效:   引起这个原因的主要因素是高并发下,我们一般设定一个缓存的过期时间时,可能有一些会设置5分钟啊,10分钟这些;并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间在同一时刻,这个时候就可能引发——当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。   处理方法: 一个简单方案就是将缓存失效时间分散开,不要所以缓存时间长度都设置成5分钟或者10分钟;比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。 缓存失效时产生的雪崩效应,将所有请求全部放在数据库上,这样很容易就达到数据库的瓶颈,导致服务无法正常提供。尽量避免这种场景的发生。 缓存穿透:   出现场景:指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

  当在流量较大时,出现这样的情况,一直请求DB,很容易导致服务挂掉。 处理方法:   方法1.在封装的缓存SET和GET部分增加个步骤,如果查询一个KEY不存在,就已这个KEY为前缀设定一个标识KEY;以后再查询该KEY的时候,先查询标识KEY,如果标识KEY存在,就返回一个协定好的非false或者NULL值,然后APP做相应的处理,这样缓存层就不会被穿透。当然这个验证KEY的失效时间不能太长。

  方法2.如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,一般只有几分钟。

  方法3.采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。

缓存并发:   出现场景:当网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。   处理方法:对缓存查询加锁,如果KEY不存在,就加锁,然后查DB入缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入DB查询。

解决方案

直接撸代码 下面是引用类 StudentController

代码语言:javascript
复制
<?php   class StudentController extends BaseController{         //网站首页展示         public function index(){            //获取分数最高的10位学生信息            $top10ScoreStudents = StudentService::getTop10Students();            //todo something more detail...         }  }

接着是关键的服务类 StudentService

代码语言:javascript
复制
<?phpnamespace App\Service;use App\Base\Service as BaseService;//通常来说一个稍大型的 PHP 项目,都有有一个仓储层 Repositoryuse App\Repository\Studuent as StudentRepository;class StudentService extends BaseService{    public static function getTop10Students(){      $config = App::getConfig();      $cacheKeyGenerator = CacheKeyGenerator::getInstance();      //使用通用的缓存键生成器去获取缓存新键,方便增加统一的前缀(可用于包含 app 名字和版本信息)      $top10StuduentsCacheKey = $cacheKeyGenerator->generate($config["cacheKeyAlias"]["top10Students"]["key"]);     //下面是常见的缓存获取代码     //从 redis client 连接池实例中获取一个可用的 client 连接,保持连接复用的连接池技术     $redisInstance = RedisInstancePool::getInstance();     //直接从缓存中获取,若为 null ,则更新缓存     $top10StuduentsCache = $redisInstance->get($top10StuduentsCacheKey );    //这里使用 === null 判断是为了避免空数据导致的缓存穿透    if ($top10StuduentsCache === null){      //从 db 中获取一份最新的缓存数据      //加一个访问锁,最多锁 20 秒,因为一个并发 1000 左右的单秒访问此接口时,若不加锁      //必然会导致直接多个请求直接命中数据库,也就是下面的 “StudentRepository::getTop10Students()”,明显可能会导致数据库瓶颈出现      if($lockForTop10Students = LockUtility::lock($config["cacheKeyAlias"]["top10Students"]["key"]),20){          $top10StudentsFromDb = StudentRepository::getTop10Students();          if(empty($top10StudentsFromDb) {              //防止空数据穿透到数据库              $top10StudentsFromDb = [];          }          $top10StuduentsCache = $top10StudentsFromDb;         //设置缓存         //ttl 是保存在全局 config 中的 redis key 生命周期         $redisInstance->set($config["cacheKeyAlias"]["top10Students"]["key"],$top10StudentsFromDb,$config["cacheKeyAlias"]["top10Students"]["ttl"]);         LockUtility::unlock($config["cacheKeyAlias"]["top10Students"]["key"]);         return $top10StuduentsCache;       } else {          //没有获取到访问锁,直接返回空结果          return [];       }     }     return $top10StuduentsCache;  }}

参考链接:

  1. https://www.cnblogs.com/gauze/p/6679561.html
  2. https://www.cnblogs.com/lingshao/p/5658757.html
  3. http://wiki.jikexueyuan.com/project/openresty/lock/cache-miss-storm.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-03-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP技术大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题描述
  • 解决方案
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档