场景:
高并发情况下,缓存失效瞬间,大量请求落到db情况下,会造成雪崩效应,如何解决?
思路:
设置一个长时间的缓存时间,再设置一个短时间的刷新缓存时间,如果到了刷新缓存的时间,则在redis中加一个原子锁,查询db数据,设置缓存,并返回数据。
/**
* 防止缓存穿透解决方法
*
* @param string $cacheKey
* @param int $expireTime 过期时间
* @param int $refreshTime 刷新缓存时间
* @param \Closure $callback 查询db回调
* @return array|mixed
*/
function refresh_cache(string $cacheKey, int $expireTime, int $refreshTime, \Closure $callback):array
{
$lock = $cacheKey. ':lock'; //缓存锁的key
$ttl = Reids::ttl($cacheKey); //以秒为单位返回 key 的剩余过期时间
$swooleResponse = new Swoole\Http\Response();
//如果存在key
if ($ttl > 0) {
$cacheData = Reids::get($cacheKey);
$data = $cacheData ? json_decode($cacheData, true) : [];
$diffTime = bcsub($expireTime, $ttl);
/** 如果缓存过去的时间,在刷新的时间内,返回缓存数据 */
if ($diffTime < $refreshTime) {
$swooleResponse->header('X-Cache-Ttl', $ttl); //或 \header('X-Cache-Ttl: xx');
return $data;
}
/** 设置redis原子锁, 如果100个用户同时访问,只会一个用户执行 */
if (Redis::set($lock, 'locked', 'nx', 'ex', 3)) {
//执行回调函数,查询db,并设置缓存
$data = $callback();
Redis::set($cacheKey, json_encode($data, JSON_UNESCAPED_UNICODE), $expireTime);
$swooleResponse->header('X-Cache-Ttl', 'Void');
return $data;
}
$swooleResponse->header('X-Cache-Ttl', $ttl);
return $data;
}
//缓存不存在,执行回调函数,查询db,并设置缓存
$data = $callback();
Redis::set($cacheKey, json_encode($data, JSON_UNESCAPED_UNICODE), $expireTime);
$swooleResponse->header('X-Cache-Ttl', 'None');
return $data;
}
- 如何使用:
//例如:获取用户列表,设置1小时缓存,如果缓存过去大于30s, 则查询一次db
$callback = function () {
return $db->query("SELECT * FROM `user` LIMIT 10")->get();
};
return refresh_cache('user:list:cache:key', 3600, 30, $callback);