在php中,防止缓存穿透、击穿和雪崩的核心策略分别是布隆过滤器或空值缓存、互斥锁或逻辑过期、以及随机ttl和限流降级。这三者的根本区别在于故障的成因和影响范围:穿透是查询不存在的数据导致请求直达数据库,击穿是热点数据过期瞬间大量请求涌入,而雪崩则是大量缓存键在同一时间失效引发系统过载。

如何防止缓存穿透
防止缓存穿透最直接的做法是在缓存中为不存在的Key设置一个短生存时间的空值(Null Cache),或者使用布隆过滤器拦截非法请求。当PHP代码查询缓存未命中时,不应直接查库并返回空,而是将空结果写入Redis,从而避免后续相同请求反复打到数据库。
public function get($key) {
$value = Redis::get($key);
if ($value === null) {
// 假设查库结果为null
$dbValue = $this->db->query($key);
if ($dbValue === null) {
// 写入空值缓存,防止穿透
Redis::setex($key, 60, '');
} else {
Redis::setex($key, 3600, $dbValue);
}
return $dbValue;
}
return $value;
}
上述代码展示了典型的空值缓存逻辑。当`$dbValue`为空时,依然调用`setex`将空字符串存入Redis,并设置较短的过期时间(如60秒)。这样在60秒内,相同的非法请求会直接从缓存获取空值,不会触发数据库查询。若使用布隆过滤器,则在查询前先判断Key是否存在于布隆过滤器中,若不存在则直接返回,不再进行后续操作。
如何防止缓存击穿
防止缓存击穿的常见方案是使用互斥锁(Mutex Lock)或逻辑过期。当发现热点Key过期时,只有一个线程去重建缓存,其他线程等待或走旧数据。在PHP中,可以利用Redis的`SETNX`命令实现分布式锁。
public function getHotKey($key) {
$value = Redis::get($key);
if ($value === null) {
// 尝试获取锁
$isLock = Redis::setnx('lock:' . $key, 1, ['nx', 'ex' => 10]);
if ($isLock) {
// 获取锁成功,重新加载缓存
$dbValue = $this->db->query($key);
Redis::setex($key, 3600, $dbValue);
Redis::del('lock:' . $key);
return $dbValue;
} else {
// 获取锁失败,休眠后重试
usleep(50000);
return $this->getHotKey($key);
}
}
return $value;
}
这段代码通过`setnx`确保同一时刻只有一个请求执行数据库查询。如果`$isLock`为真,当前线程负责从数据库读取数据并更新缓存;如果为假,说明有其他线程正在重建缓存,当前线程短暂休眠后递归调用自身再次尝试获取缓存。这种方式有效避免了大量并发请求同时击穿缓存层到达数据库。
如何防止缓存雪崩
防止缓存雪崩的关键在于避免大量Key同时过期,并增加系统的容错能力。具体做法包括给缓存的TTL添加随机值,以及使用多级缓存或限流降级机制。
// 设置TTL时添加随机偏移量
$baseTtl = 3600;
$randomOffset = rand(0, 600); // 随机增加0-600秒
Redis::setex($key, $baseTtl + $randomOffset, $value);
// 简单的熔断降级示例
if (!Redis::ping()) {
return $this->fallbackStrategy(); // 返回默认值或静态页面
}
在设置缓存过期时间时,不要统一使用固定时长,而是基于基础TTL加上一个随机数,这样可以打散Key的过期时间,避免集中失效。同时,代码中加入了`Redis::ping()`检查,当Redis集群不可用时,直接触发降级逻辑,返回兜底数据,防止因缓存服务宕机导致所有请求涌向数据库,从而保护后端服务不被压垮。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











