本文通过阅读 原文 此文进行整理,看原文的同学们请移步至此
一 缓存穿透
缓存穿透指的是,当我们访问某个缓存 KEY 想取得对应的数据时,若此 KEY 不存在于缓存中,则会去查库。如何解决呢?将每次查询的结果都放入缓存不管是不是空。
public function getArticles($key)
{
$expire = 60 * 3;
$data = Cache::get($key);
// 注意:此处使用 is_null 来判断而不是直接使用 (!$data)来判断。// 使用 (!$data)来判断的弊端是:如果 $data 的值为空字符串或者空数组,此处也是不成立的,会继续执行查询 DB 的语句,造成缓存穿透
if (!is_null($data)) {return $data;}
$data = $this->searchDB();
Cache::put($key, $data, $expire);
return $data;
}
这样处理的原因是,即使当前查询的 key 为空字符串,或者空数组,结果也会被缓存起来。当下一次访问时会直接返回,不会造成缓存穿透
二 缓存加锁(Redis)
若系统的并发很高,当缓存过期时,则大量的请求会穿透缓存,同时到 DB 中查询,那我们可以设置缓存当缓存过期时,只去 DB 中请求一次并缓存吗?可以,我们可以使用 redis 的 setNx()setNx($key) 的作用类似于 set($key),setNx 的意思为 set Not Exists 如果 $key 不存在则设置,存在则不进行任何操作. 设置成功设置返回 1,说明当前的请求获得了当前的操作权限,设置失败返回 0,说明此资源已经被其他请求获得。
使用代码实现的话,思路如下:
- 给存入缓存的数据增加一个过期时间字段暂时给这个字段起名字叫 $data[‘expire’](这个过期时间要短于实际的缓存过期时间),方便在缓存过期前执行加锁和缓存更新。
- 如果 $data[‘expire’]达到过期时间,则执行加锁以及缓存更新。
- 此时如果有其他请求进入则返回更新之前的数据。
代码如下:
public function getArticlesLock($key)
{$time = time();
$expire = 10 * 2;
$lockKey = 'lock:k';
$data = Cache::get($key);
if (!is_null($data)) {
// 缓存未过期
if ($data['expire'] > time()){return $data['data'];
}
// 加锁失败说明已经有请求执行加锁,返回之前的缓存数据
if (!Redis::setnx($lockKey,1)) {return $data['data'];
}
}
sleep(3);
$datat = $this->searchDB();
$data = [
'data' => $datat,
'expire' => $time + $expire - 10
];
$r = Cache::put($key, $data, $expire);
// 解锁
Redis::del($lockKey);
return $data['data'];
}
当然此处也可以使用 set()
来代替 setnx()
加锁,以及使用 lua 脚本解锁。