缓存穿透(Cache Penetration)是指缓存和数据库都未命中时,直接访问数据库,从而对数据库产生很大压力。我们可以采取以下几种策略来防止缓存穿透【采用布隆过滤器BloomFilter、缓存空值、缓存雪崩】:
1. 采用布隆过滤器(Bloom Filter)
布隆过滤器是一种空间效率高的概率数据结构,用于检测一个元素是否在一个集合中。它可以有效防止缓存穿透,将不存在的请求直接拦截掉。
实现示例:
首先,我们需要安装一个PHP的布隆过滤器库,例如 php-bloom-filter
。可以通过Composer安装:
composer require ivan-novakov/php-bloom-filter
然后,可以实现一个简单的布隆过滤器:
require 'vendor/autoload.php';
use BloomFilter\BloomFilter;
// 创建布隆过滤器
$bloomFilter = new BloomFilter(1000000, 0.01); // 100万元素,1%的误判率
// 将所有存在的key添加到布隆过滤器
$existingKeys = ['key1', 'key2', 'key3']; // 假设这些key是存在的
foreach ($existingKeys as $key) {
$bloomFilter->add($key);
}
// 查询数据时使用布隆过滤器
function getDataWithBloomFilter($key) {
global $bloomFilter;
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
if (!$bloomFilter->has($key)) {
// 如果布隆过滤器判断不存在该key,直接返回空或默认值
return null;
}
// 检查缓存
$value = $redis->get($key);
if ($value !== false) {
return $value;
}
// 缓存中没有,查询数据库
$value = loadFromDatabase($key);
// 如果数据库中有值,设置缓存
if ($value !== null) {
$redis->set($key, $value, 600); // 设置缓存有效期为600秒
} else {
// 如果数据库中没有值,缓存空值并设置较短的过期时间
$redis->set($key, '', 60); // 空值缓存有效期为60秒
}
return $value;
}
function loadFromDatabase($key) {
// 模拟从数据库加载数据
if ($key === 'key1') {
return '数据库中的数据';
}
return null;
}
布隆过滤器也可以自己封装,就是一个很大的数组包含了你要缓存的可能出现的所有键就这么简单,也不定要封装,你也可以将过滤器的所有值永久存到redis,或者直接写到一个数组里面都可以
2. 缓存空值
当数据库查询结果为空时,缓存空值以避免反复查询数据库。设置短时间的过期时间来减少数据库压力。
实现示例:
function getDataWithEmptyCache($key) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 检查缓存
$value = $redis->get($key);
if ($value !== false) {
return $value !== '' ? $value : null;
}
// 缓存中没有,查询数据库
$value = loadFromDatabase($key);
if ($value !== null) {
// 数据库中有值,设置缓存
$redis->set($key, $value, 600); // 有效期为600秒
} else {
// 数据库中没有值,缓存空值并设置较短的过期时间
$redis->set($key, '', 60); // 空值缓存有效期为60秒
}
return $value;
}
3. 接口限流与熔断、降级
限流策略可以防止恶意攻击请求过多对系统造成压力,熔断和降级则在服务不可用或负载过高时启用备用策略。
实现示例:
使用 Rate Limiter
来限制接口访问频率,例如采用 Redis
实现一个简单的限流策略【点击查看6种限流算法】:
function isRateLimited($userId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$key = "rate_limit:{$userId}";
$limit = 100; // 每分钟限制100次请求
$currentCount = $redis->get($key);
if ($currentCount !== false && $currentCount >= $limit) {
return true; // 被限流
}
// 如果没有超限,则增加计数
$redis->multi()
->incr($key)
->expire($key, 60) // 计数器1分钟有效期
->exec();
return false;
}
function handleRequest($userId, $key) {
if (isRateLimited($userId)) {
return "Rate limit exceeded, please try again later.";
}
return getDataWithEmptyCache($key);
}
总结
这些方法可以有效防止缓存穿透问题:
- 布隆过滤器:用于快速检测不存在的key,减少无效查询。
- 缓存空值:避免对数据库的重复查询压力。
- 接口限流、熔断、降级:保护系统在高负载情况下的稳定性。
通过合理组合这些策略,可以显著提升系统的稳定性和可用性,尤其是在高并发的环境中。