在项目开发过程中,我们们经常会用到缓存来加速项目访问、减小数据库压力,但是在使用缓存过程会有一些常见的问题。今天我们就来聊聊关于缓存那些事。
说明
以下示例代码均基于 PHP 的 Laravel 框架完成
一般缓存会用在程序从数据库中查询数据之前,先从缓存中取数据,如果缓存中有相应数据,就直接返回缓存中的数据,否则从数据库中查询数据并存储到缓存,最后将查询到的结果返回。代码示例如下:
function cache_remember($cacheId, $second, Closure $callback){
if($second === null){
return $callback();
}
$data = Cache::get($cacheId);
if($data === null){
$data = $callback();
if($data !== null){
Cache::set($cacheId, $second, $data);
}
}
return $data;
}
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样避免用户请求的时候再去加载相关的数据。可以通过定时任务,定时预热、刷新缓存
缓存穿透是指当用户反复查询一个数据库中不存在的数据,缓存系统自然也没有,这时候这样的无效请求就会压到数据库。如果系统有大量这样的请求,数据库压力自然也会增大
解决缓存穿透,主要有下面几个方法
详解布隆过滤器的原理、使用场景和注意事项
缓存击穿是指当某个缓存数据过期时,如果此时突然有大量请求进来,直接访问到数据库,造成数据库的较大压力。
解决缓存击穿,主要有以下方法
代码示例
function cache_remember($cacheId, $second, Closure $callback){
if($second === null){
return $callback();
}
$data = Cache::get($cacheId);
if($data !== null){
return null;
}
$lock = Cache::lock($cacheId.'_lock', 10);
try {
$lock->block(5);
$data = Cache::get($cacheId);
if($data === null){
$data = $callback();
if($data !== null){
Cache::set($cacheId, $second, $data);
}
}
// Lock acquired after waiting maximum of 5 seconds...
} catch (LockTimeoutException $e) {
// Unable to acquire lock...
} finally {
optional($lock)->release();
}
return $data;
}
function cache_remember($cacheId, $second, Closure $callback){
if($second === null){
return $callback();
}
$signKey = $cacheId.'_sign';
$data = Cache::get($cacheId);
$sign = Cache::get($sign);
//缓存标记没有失效,返回缓存数据
if($sign !== null){
return $data;
}
Cache::set($signKey, 1, $second);
//缓存标记失效,通过子进程更新缓存
$cPid = pcntl_fork();
if($cPid == 0){
$data = $callback();
Cache::set($cacheId, $data, $second * 2);
exit();
}
//如果旧的缓存数据存在,暂时返回旧的数据
if($data !== null){
return $data;
}
//等待进程结束,获取新的缓存数据返回
pcntl_wait($status);
return Cache::get($cacheId);
}
如上面代码所示,我们通过缓存标记可以标记将要过期的缓存,然后通过子进程更新缓存,系统在此期间仍然可以获取之前的缓存数据返回。
说明
PHP 的多进程任务处理并不适合使用在 WEB 系统,这里只是展示缓存标记的实现思路,在实际的 WEB 系统中,可以通过将要过期的缓存信息写入 Redis 队列,通过常驻后台的 PHP 进程来监听队列更新这些缓存。
缓存雪崩是指由于缓存集中过期或者缓存服务器宕机导致大量缓存不可用从而请求直接压到数据库服务器上的现象。
如果缓存数据是通过预热加载进缓存系统的,并且在缓存过期之前,没有再次将新鲜的缓存加载到系统,缓存过期时,就有可能造成雪崩;或者如果短时间有大量数据更新,也会因为大量缓存失效造成雪崩。
对于缓存雪崩,有如下解决方案
缓存是系统开发中的重要内容,能够非常有效地降低数据库的压力,加速系统访问。因此有许多开发者将缓存当做万金油一样的存在,系统哪里访问慢,就通过加缓存的来解决。缓存的滥用同时也会给系统带来许多问题和隐患,先不说缓存更新不及时带来的数据陈旧问题,缓存的滥用会掩盖系统许多设计和编码的问题,而这些问题随着业务的发展随时会爆发。
那么我们用该如何使用缓存呢?
首选在业务发展的初期,系统最好不要大量使用缓存。因为业务发展初期,访问压力并不是特别大,业务也不会特别复杂,如果这时候系统在没有大量缓存的情况无法让用户正常使用,那系统设计和编码肯定有问题,这时候需要考虑的是如何优化设计和编码,而不是通过增加缓存来暂时掩盖设计和编码的问题。同时,不使用缓存也能够让一些隐藏的问题及时暴露,比例通过排查数据库慢查询日志发现某些查询性能低下的查询语句。
当业务发展到一定的规模,系统复杂度和访问压力逐渐增大,数据库查询逐渐成为性能瓶颈的时候,通过加缓存能够为我们赢来系统优化和升级的时间(因为给系统加缓存是很快的,并且能够在很长的一段时间内解决系统访问慢的问题)。而如果一开始系统就大量使用了缓存,这时候就很难快速优化系统,整体优化和升级也会手忙脚乱。