Redis是一种流行的内存缓存解决方案,但在实际使用中,会遇到一些缓存相关的问题,其中最主要的就是缓存穿透,击穿和雪崩问题。
一、缓存穿透
缓存穿透是指当查询一个不存在的缓存时,由于缓存中没有相应的数据,而数据库中也不存在对应的数据,导致每次查询都必须访问数据库,这会导致数据库的负载过高。攻击者可以利用这个漏洞进行恶意攻击,通过不断查询不存在的缓存,来使缓存服务器或者数据库瘫痪。
关于缓存穿透常用的的有两种解决办法:
第一种,缓存空数据,当查询一个不存在的数据时,可以将这个查询结果缓存起来,以空对象的形式存储在缓存中。这样,在下一次查询时,如果缓存中存在这个空对象,就可以直接返回,而不会再次查询数据库或接口。
第二种,使用布隆过滤器
<?php
/**
* 布隆过滤器类
*/
class BloomFilter {
private $redis; // Redis对象
private $bitmapSize; // 位图大小
private $hashCount; // 哈希函数数量
/**
* 构造函数
* @param int $bitmapSize 位图大小,默认为1024
* @param int $hashCount 哈希函数数量,默认为3
*/
public function __construct($bitmapSize = 1024, $hashCount = 3) {
// 实例化Redis对象并连接到Redis服务器
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$this->redis = $redis;
$this->bitmapSize = $bitmapSize;
$this->hashCount = $hashCount;
}
/**
* 插入元素到布隆过滤器中
* @param string $element 要插入的元素
*/
public function insert($element) {
// 对元素进行多次哈希,并在位图中设置相应位置为1
for ($i = 1; $i <= $this->hashCount; $i++) {
$hash = md5($element . $i);
$position = hexdec(substr($hash, 0, 6)) % $this->bitmapSize;
$this->redis->setBit('bloom_filter', $position, 1);
}
}
/**
* 判断元素是否存在于布隆过滤器中
* @param string $element 要判断的元素
* @return bool 如果存在返回true,否则返回false
*/
public function contains($element) {
// 对元素进行多次哈希,并检查位图相应位置是否为1
for ($i = 1; $i <= $this->hashCount; $i++) {
$hash = md5($element . $i);
$position = hexdec(substr($hash, 0, 6)) % $this->bitmapSize;
if (!$this->redis->getBit('bloom_filter', $position)) {
// 如果任意一个位置为0,则表示元素不存在于布隆过滤器中
return false;
}
}
// 如果所有位置都为1,则表示元素可能存在于布隆过滤器中
return true;
}
}
// 缓存穿透解决方案
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 实例化布隆过滤器对象
$bloomFilter = new BloomFilter($redis);
$cacheKey = 'product_info_123';
if ($bloomFilter->contains($cacheKey)) {
$productInfo = $redis->get($cacheKey);
if ($productInfo) {
return $productInfo;
}
} else {
// 如果布隆过滤器中不存在该键,则直接返回空结果
return null;
}
// 如果缓存中不存在该键,则从数据库中查询并存入缓存和布隆过滤器中
$productInfo = $database->queryProductInfoById(123);
if (!$productInfo) {
// 如果数据库中也不存在该记录,则插入一个空记录,防止缓存穿透攻击
$productInfo = '';
$redis->set($cacheKey, 60, $productInfo);
} else {
$redis->set($cacheKey, 60, $productInfo);
$bloomFilter->insert($cacheKey);
}
return $productInfo;
二、缓存击穿
缓存击穿是指当某个热点数据失效时,大量的并发请求访问这个热点数据,导致所有请求都访问数据库,从而使数据库的负载急剧增加。为了避免这种情况发生,可以将热点数据的缓存时间设置为永不过期,或者设置一个较长的过期时间。
$redis->set($key, $value, -1) 和$redis->set($key, $value) 都可以用来设置缓存数据,并且让缓存永不过期。
三、缓存雪崩
缓存雪崩是指当某一时刻缓存中的大量数据同时失效时,大量的并发请求访问这些失效的数据,导致所有请求都访问数据库,从而使数据库的负载急剧增加。为了避免这个问题,我们可以设置缓存的过期时间随机化,使缓存的失效时间不同,避免大量数据同时失效。此外,我们也可以采用缓存预热的方式,提前将热点数据加载到缓存中,以减少缓存的失效率。
在实际应用中,可以通过设置一个随机数生成器,随机生成一个缓存时间偏移量,然后将缓存时间加上偏移量作为实际缓存时间。例如,在一个缓存时间为60秒的基础上,可以随机生成一个0到10秒的偏移量,然后将缓存时间设置为60秒加上该偏移量,即在60到70秒之间随机。
// 缓存时间
$cache_time = 60;
// 生成随机偏移量
$offset = rand(0, 10);
// 设置缓存时间
$expire_time = $cache_time + $offset;
// 设置缓存
$redis->set($key, $value, $expire_time);
{{item.user_info.nickname ? item.user_info.nickname : item.user_name}}
作者 管理员 企业
{{itemf.name}}
{{itemc.user_info.nickname}}
{{itemc.user_name}}
回复 {{itemc.comment_user_info.nickname}}
{{itemf.name}}