缓存穿透、缓存并发、缓存失效问题以及解决方案

一、缓存穿透

问题描述:我们在项目中使用缓存通常都是APP先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB,这样缓存就失去了意义,在流量大时,可能DB就挂掉了。

解决方法

    方法1.在封装的缓存SET和GET部分增加个步骤,如果查询一个KEY不存在,就已这个KEY为前缀设定一个标识KEY;以后再查询该KEY的时候,先查询标识KEY,如果标识KEY存在,就返回一个协定好的非false或者NULL值,然后APP做相应的处理,这样缓存层就不会被穿透。当然这个验证KEY的失效时间不能太长。

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

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

 

二、缓存并发

问题描述:有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询DB,同时设置缓存的情况,如果并发确实很大,这也可能造成DB压力过大,还有缓存频繁更新的问题。

解决方法

  方法1.单独的离线进程更新缓存内容,其他进程只负责读取缓存数据,不负责更新缓存

  方法2.利用锁来控制只有其中一个进程执行数据库查询并更新缓存,其他进程取原缓存的数据或者等待缓存更新后再从缓存取数据。参考https://huoding.com/2015/09/14/463

  伪代码:

$cacheKey;
$expire;
$cacheTtl;

$lockKey;
$lockExpire;

$cache  = $redis->get($cacheKey);
$ttl = $redis->ttl($cacheKey);
$lockValue = $random;

if ($ttl < $cacheTtl && $redis->set($lockKye, $lockValue, ['nx', 'ex' => $lockExpire])) {
	$cache->update();
	if ($redis->get($lockKey) == $lockValue) {
		$redis->del($lockKey);
	}
}

 

三、缓存失效

问题描述:引起这个问题的主要原因还是高并发的时候,平时我们设定一个缓存的过期时间时,可能有一些会设置5分钟啊,10分钟这些;并发很高时可能会出在某一个时间同时生成了很多的缓存,并且过期时间都一样,这个时候就可能引发一当过期时间到后,这些缓存同时失效,请求全部转发到DB,DB可能会压力过重。

解决方法:一个简单方案就是将缓存失效时间分散开,不要所以缓存时间长度都设置成5分钟或者10分钟;比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

 

第二、第三个问题其实差不多,主要就时第二个问题时针对同一个缓存,第三个问题时针对很多缓存。

posted @ 2017-04-07 18:47  奔跑的大白  阅读(1094)  评论(0编辑  收藏