概念
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。
解决方案
假设缓存数据库是Redis,比如我们查询用户45709823的用户信息,
不管我们是否查到用户,我们都会这么设置
原理
缓存存取逻辑一般比较简单。
- 查缓存,缓存存在直接返回
- 缓存不存在,查数据库获取数据(这一步会有问题),然后写入缓存。
其中第二步的查数据库获取数据会有问题,假设当前有个人,知道你用户id没有负数开头的,我直接请求你获取用户信息接口,http:/api.com?uid=-123456
这时候你没有任何防缓存穿透的策略,
第一步:查uid=-123456
的缓存,缓存不存在
第二步:查数据库uid=-123456
的记录,当然肯定也查不到,查不到就不会写入缓存。
现在别人写个脚本开1w个线程请求你接口,相当于你现在的缓存是没有用的,请求全部落到DB中查询不存在的数据,当前你数据库就处于缓存穿透的情况中。压力一大DB就会宕机。
这时候有个非常简单的办法,不管请求数据是否存在,你都要写入缓存一个唯一标识,比如0,查不到数据你也会写入标识,下次别人重复请求接口,他拿到的是缓存数据,这时候代码中可以判断是否有用户信息,如果没有用户信息,只有防缓存穿透的标识,这时候你可以扔报错或者其他逻辑。
代码示例
// 缓存穿透
$user = $redis->get('uid_xxx');
if (!$user) {
$value = $db->query("select * from user = 'uid_xxx'");
if ($user) {
$redis->set('uid_xxx', $value);
}
}
// 防止缓存穿透
$user = $redis->get('uid_xxx');
if (!$user) {
$value = [
'_' => [0],
];
$row = $db->query("select * from user = 'uid_xxx'");
if ($user) {
$value['user'] = $row;
}
$redis->set('uid_xxx', $value);
}