缓存穿透问题及解决办法
- 问题描述:缓存穿透指查询一个一定不存在的数据,由于缓存未命中,就会去查询数据库,若每次都查询数据库,当高并发时会对数据库造成巨大压力。
- 解决办法 - 设置缓存过期策略:
- 空值缓存:当查询数据库发现数据不存在时,也将空值缓存起来,并设置一个较短的过期时间,防止一直查询数据库。例如,在Node.js中使用
node-cache
模块:
const NodeCache = require('node-cache');
const myCache = new NodeCache();
async function getData(key) {
let data = myCache.get(key);
if (data === undefined) {
// 假设这是查询数据库的函数
data = await queryDatabase(key);
if (data === null) {
// 缓存空值,设置较短过期时间,比如1分钟
myCache.set(key, null, 60);
} else {
myCache.set(key, data);
}
}
return data;
}
async function queryDatabase(key) {
// 模拟数据库查询,这里返回null表示数据不存在
return null;
}
- 解决办法 - 缓存预热:
- 布隆过滤器:在缓存预热阶段,使用布隆过滤器来判断数据是否存在。布隆过滤器可以快速判断一个元素是否在一个集合中,误判率很低。当查询时,先通过布隆过滤器判断,如果不存在则直接返回,避免查询数据库。以
bloom-filters
模块为例:
const BloomFilter = require('bloom-filters');
const myCache = new NodeCache();
// 初始化布隆过滤器,预计元素数量和误判率
const bf = new BloomFilter(1000, 0.01);
// 缓存预热,假设从数据库获取所有存在的key
const allKeys = await getAllKeysFromDatabase();
allKeys.forEach(key => {
bf.add(key);
// 同时缓存数据
const data = await queryDatabase(key);
myCache.set(key, data);
});
async function getData(key) {
if (!bf.has(key)) {
return null;
}
let data = myCache.get(key);
if (data === undefined) {
data = await queryDatabase(key);
myCache.set(key, data);
}
return data;
}
async function queryDatabase(key) {
// 模拟数据库查询
return { value: 'data' };
}
async function getAllKeysFromDatabase() {
// 模拟获取所有数据库中的key
return ['key1', 'key2', 'key3'];
}
缓存雪崩问题及解决办法
- 问题描述:缓存雪崩指在某一时刻,大量的缓存数据同时过期,导致大量请求直接访问数据库,造成数据库压力过大甚至崩溃。
- 解决办法 - 设置缓存过期策略:
- 随机过期时间:避免设置固定的过期时间,而是在一个合理的时间范围内设置随机的过期时间。例如:
const NodeCache = require('node-cache');
const myCache = new NodeCache();
async function setData(key, data) {
// 设置10 - 20分钟之间的随机过期时间
const randomExpiry = Math.floor(Math.random() * (20 - 10 + 1) + 10) * 60;
myCache.set(key, data, randomExpiry);
}
async function getData(key) {
let data = myCache.get(key);
if (data === undefined) {
data = await queryDatabase(key);
await setData(key, data);
}
return data;
}
async function queryDatabase(key) {
// 模拟数据库查询
return { value: 'data' };
}
- 解决办法 - 缓存预热:
- 分级缓存:在缓存预热时,将数据分级缓存。例如,一级缓存设置较短的过期时间,二级缓存设置较长的过期时间。当一级缓存过期后,先从二级缓存获取数据,同时重新设置一级缓存。
const NodeCache = require('node-cache');
const firstLevelCache = new NodeCache();
const secondLevelCache = new NodeCache();
async function setData(key, data) {
// 一级缓存设置较短过期时间,如10分钟
firstLevelCache.set(key, data, 10 * 60);
// 二级缓存设置较长过期时间,如1小时
secondLevelCache.set(key, data, 60 * 60);
}
async function getData(key) {
let data = firstLevelCache.get(key);
if (data === undefined) {
data = secondLevelCache.get(key);
if (data!== undefined) {
// 重新设置一级缓存
firstLevelCache.set(key, data, 10 * 60);
} else {
data = await queryDatabase(key);
await setData(key, data);
}
}
return data;
}
async function queryDatabase(key) {
// 模拟数据库查询
return { value: 'data' };
}