Redis缓存雪崩和缓存穿透是两种常见的缓存相关问题,它们的区别和解决方案如下:
-
缓存雪崩(Cache Avalanche):
- 定义:缓存雪崩指的是在缓存中大量的缓存数据同时失效或者在同一时间段内请求了大量未命中的数据,导致大量请求直接落到数据库上,造成数据库压力剧增,甚至导致数据库宕机。
- 原因:缓存中的数据在同一时间段内大规模地失效或未命中,导致大量的请求落到数据库上。
- 解决方案:
- 设置合适的过期时间:尽量避免设置所有缓存数据的过期时间相同,分散过期时间可以减少缓存雪崩的风险。
- 热点数据预加载:针对热点数据提前加载到缓存中,避免大量请求同时落到数据库上。
- 限流和降级:在缓存雪崩发生时,通过限制请求的并发量或者进行服务降级来减轻数据库压力。
假设我们有一个简单的电子商务网站,其中有一个商品信息页面,需要频繁地查询商品信息,我们可以通过缓存来提高性能。为了避免缓存雪崩问题,我们可以设置合适的过期时间,分散过期时间。以下是一个示例:
<?php
class Cache {
private $cache = array();
public function get_data($key) {
return isset($this->cache[$key]) ? $this->cache[$key] : null;
}
public function set_data($key, $value, $ttl) {
$this->cache[$key] = $value;
// 设置过期时间,并增加一些随机性
$randomFactor = rand(0, 300); // 增加0到5分钟的随机时间
if ($ttl > 0) {
$this->cache[$key . ':ttl'] = time() + $ttl + $randomFactor;
}
}
public function is_expired($key) {
// 判断缓存是否过期
return isset($this->cache[$key . ':ttl']) && time() > $this->cache[$key . ':ttl'];
}
}
// 模拟数据库查询函数
function fetch_data_from_database($productId) {
echo "Querying database for product with ID: " . $productId . "\n";
// 模拟查询数据库
sleep(1);
return "Product data for ID " . $productId;
}
// 主函数
function main() {
$cache = new Cache();
// 模拟商品信息页面请求
for ($productId = 1; $productId <= 10; $productId++) {
$cacheKey = 'product_' . $productId;
// 如果缓存中没有数据或者数据已过期,则从数据库获取数据
if ($cache->get_data($cacheKey) === null || $cache->is_expired($cacheKey)) {
$productData = fetch_data_from_database($productId);
// 设置合适的过期时间,例如设置为1小时,但增加一些随机性
$cache->set_data($cacheKey, $productData, 3600); // 设置过期时间为1小时
}
// 获取缓存中的商品数据并输出
echo "Product data for ID " . $productId . ": " . $cache->get_data($cacheKey) . "\n";
}
}
main();
?>
下面是一个用PHP编写的示例,演示如何使用合适的过期时间和热点数据预加载来解决缓存雪崩问题:
<?php
// 模拟缓存类
class Cache {
private $cache = array();
public function get_data($key) {
return isset($this->cache[$key]) ? $this->cache[$key] : null;
}
public function set_data($key, $value, $ttl) {
$this->cache[$key] = $value;
// 设置过期时间
if ($ttl > 0) {
$this->cache[$key . ':ttl'] = time() + $ttl;
}
}
public function is_expired($key) {
// 判断缓存是否过期
return isset($this->cache[$key . ':ttl']) && time() > $this->cache[$key . ':ttl'];
}
}
// 模拟数据库查询函数
function fetch_data_from_database($key) {
echo "Querying database for key: " . $key . "\n";
// 模拟查询数据库
sleep(1);
return "Data for " . $key;
}
// 模拟热点数据预加载函数
function preload_hot_data($cache) {
echo "Preloading hot data...\n";
$hot_keys = ['key1', 'key2', 'key3']; // 假设这些是热点数据的键
foreach ($hot_keys as $key) {
$data = fetch_data_from_database($key);
$cache->set_data($key, $data, 60); // 设置过期时间为60秒
}
}
// 主函数
function main() {
$cache = new Cache();
preload_hot_data($cache); // 预加载热点数据
// 模拟业务请求
for ($i = 0; $i < 10; $i++) {
$key = 'key' . rand(1, 5); // 随机选择键
if ($cache->get_data($key) === null || $cache->is_expired($key)) {
$data = fetch_data_from_database($key);
$cache->set_data($key, $data, 60); // 设置过期时间为60秒
}
echo "Data for key " . $key . ": " . $cache->get_data($key) . "\n";
usleep(500000); // 0.5秒
}
}
main();
?>
-
缓存穿透(Cache Penetration):
- 定义:缓存穿透指的是恶意请求或者不存在的数据不断地击穿缓存直接访问数据库,导致数据库负载过高。
- 原因:请求中的键对应的数据在缓存中不存在或者请求的数据不存在于数据库中。
- 解决方案:
- 布隆过滤器(Bloom Filter):在缓存层之前添加布隆过滤器,用于快速过滤掉不存在的数据请求,减少对数据库的访问压力。
- 空值缓存:对于数据库中不存在的数据,也将其存入缓存,但设置一个较短的过期时间,避免大量无效请求继续穿透到数据库。
- 合法性校验:在业务层对请求参数进行校验,过滤掉非法请求,例如校验请求参数的格式、长度等。
总的来说,缓存雪崩是由于缓存中大量数据同时失效或未命中导致的问题,可以通过设置合适的过期时间、热点数据预加载和限流降级等方式来解决;而缓存穿透是由于恶意请求或者不存在的数据不断地击穿缓存直接访问数据库导致的问题,可以通过布隆过滤器、空值缓存和合法性校验等方式来解决。
布隆过滤器是一种数据结构,用于快速判断一个元素是否可能存在于一个集合中,其通过使用位数组和多个哈希函数来实现。在缓存中,布隆过滤器可以用来快速过滤掉不存在于缓存中的数据请求,从而减轻对数据库的访问压力,防止缓存穿透问题的发生。
以下是一个用布隆过滤器描述缓存穿透问题的例子:
假设我们有一个简单的网站,用户可以通过商品ID来查询商品信息。我们的系统使用缓存来存储商品信息以提高性能。然而,有些恶意用户可能会发起大量不存在的商品ID查询请求,导致这些请求直接穿透缓存访问数据库,造成数据库压力过大。
为了应对这个问题,我们可以引入布隆过滤器。在缓存层之前,我们使用布隆过滤器来快速判断一个商品ID是否存在于缓存中。如果布隆过滤器判断商品ID不存在于缓存中,那么我们可以立即拦截这个查询请求,而不去访问数据库,从而避免缓存穿透问题的发生。
以下是一个简单的PHP示例,演示如何使用布隆过滤器来防止缓存穿透问题:
<?php
class BloomFilter {
private $bitmap;
private $hashFunctions;
private $size;
public function __construct($size, $hashFunctions) {
$this->bitmap = array_fill(0, $size, 0);
$this->hashFunctions = $hashFunctions;
$this->size = $size;
}
public function add($item) {
foreach ($this->hashFunctions as $hashFunction) {
$hash = $hashFunction($item) % $this->size;
$this->bitmap[$hash] = 1;
}
}
public function contains($item) {
foreach ($this->hashFunctions as $hashFunction) {
$hash = $hashFunction($item) % $this->size;
if ($this->bitmap[$hash] == 0) {
return false; // 如果任何一个哈希函数得到的位为0,则认为元素不存在
}
}
return true; // 所有哈希函数得到的位都为1,认为元素可能存在
}
}
// 模拟数据库查询函数
function fetch_data_from_database($productId) {
echo "Querying database for product with ID: " . $productId . "\n";
// 模拟查询数据库
sleep(1);
return "Product data for ID " . $productId;
}
// 创建布隆过滤器
$size = 1000;
$hashFunctions = [
function($item) { return crc32($item); },
function($item) { return hash('sha256', $item); }
];
$bloomFilter = new BloomFilter($size, $hashFunctions);
// 主函数
function main($productId) {
global $bloomFilter;
// 检查布隆过滤器,如果商品ID可能不存在于缓存中,则不进行查询操作
if (!$bloomFilter->contains($productId)) {
echo "Product ID " . $productId . " might not exist in cache or database\n";
return;
}
// 如果缓存中有数据,则直接输出缓存数据
$cachedData = fetch_data_from_database($productId);
echo "Product data for ID " . $productId . ": " . $cachedData . "\n";
}
// 模拟恶意用户发起不存在的商品ID查询请求
$productIdsToQuery = [1000, 2000, 3000];
foreach ($productIdsToQuery as $productId) {
main($productId);
}
?>