缓存穿透
定义
缓存穿透是指用户请求的数据既不在缓存中,也不在数据库中。每次这样的请求都会穿透缓存,直接访问数据库,增加数据库的负载。
原因
- 恶意攻击:攻击者不断请求不存在的数据。
- 无效请求:用户请求的数据在数据库中不存在。
解决办法
1.布隆过滤器预检查
布隆过滤器是一种高效的概率型数据结构,可以快速判断某个元素是否存在。它可以在缓存之前进行预检查,过滤掉那些不可能存在的数据请求。
public class BloomFilter {
private BitSet bitSet;
private int size;
private int hashCount;
public BloomFilter(int size, int hashCount) {
this.size = size;
this.hashCount = hashCount;
this.bitSet = new BitSet(size);
}
private int hash(String item, int i) {
int hash1 = item.hashCode();
int hash2 = (hash1 >>> 16) ^ (hash1 << 1);
return Math.abs((hash1 + i * hash2) % size);
}
public void add(String item) {
for (int i = 0; i < hashCount; i++) {
bitSet.set(hash(item, i));
}
}
public boolean mightContain(String item) {
for (int i = 0; i < hashCount; i++) {
if (!bitSet.get(hash(item, i))) {
return false;
}
}
return true;
}
}
2.缓存空结果
当查询数据库未命中时,可以将空结果缓存一段时间,避免短时间内重复查询数据库。
public class CacheService {
private static final Object NULL_OBJECT = new Object();
private Map<String, Object> cache = new HashMap<>();
public Object getData(String key) {
Object data = cache.get(key);
if (data == null) {
data = queryDatabase(key);
cache.put(key, data == null ? NULL_OBJECT : data);
}
return data == NULL_OBJECT ? null : data;
}
private Object queryDatabase(String key) {
// 查询数据库逻辑
return null;
}
}
缓存击穿
定义
缓存击穿是指一个非常热门的key在缓存失效的时刻,同时有大量的并发请求到达,这些请求发现缓存失效后同时访问数据库,导致数据库压力骤增。
解决办法
1.互斥锁(Mutex)
在缓存失效时,通过互斥锁确保只有一个请求可以查询数据库并更新缓存,其他请求需要等待缓存更新完成。
public class ProductService {
private Map<String, Product> cache = new HashMap<>();
private Object lock = new Object();
public Product getProduct(String productId) {
Product product = cache.get(productId);
if (product == null) {
synchronized (lock) {
product = cache.get(productId);
if (product == null) {
product = queryDatabase(productId);
cache.put(productId, product);
}
}
}
return product;
}
private Product queryDatabase(String productId) {
// 查询数据库逻辑
return new Product(productId, "Sample Product");
}
}
2.设置热点数据永不过期
对热点数据不设置过期时间,保持缓存中的数据始终有效,避免缓存失效引发的并发访问数据库问题。
public class NewsService {
private Map<String, Integer> cache = new HashMap<>();
public int getClickCount(String newsId) {
Integer clickCount = cache.get(newsId);
if (clickCount == null) {
clickCount = queryDatabase(newsId);
cache.put(newsId, clickCount);
}
return clickCount;
}
private int queryDatabase(String newsId) {
// 查询数据库逻辑
return 100; // 示例数据
}
}
缓存雪崩
定义
缓存雪崩是指在某一时间段内,大量缓存同时失效,导致大量请求直接访问数据库,引发数据库压力骤增,甚至导致系统崩溃。
解决办法
1.缓存过期时间分散
设置缓存时,给不同的缓存键设置随机的过期时间,避免大量缓存同时失效。
public void cacheData(String key, Object data) {
int expiryTime = 3600 + new Random().nextInt(600); // 随机过期时间
cache.put(key, data, expiryTime);
}
2.双层缓存
使用本地缓存和分布式缓存双层架构,本地缓存作为第一层,分布式缓存作为第二层,减少数据库直接访问的压力。
public class DoubleLayerCache {
private Map<String, Object> localCache = new HashMap<>();
private Map<String, Object> distributedCache = new HashMap<>();
public Object getData(String key) {
Object data = localCache.get(key);
if (data == null) {
data = distributedCache.get(key);
if (data == null) {
data = queryDatabase(key);
distributedCache.put(key, data);
}
localCache.put(key, data);
}
return data;
}
private Object queryDatabase(String key) {
// 查询数据库逻辑
return "Sample Data"; // 示例数据
}
}
3.请求限流
当检测到数据库压力过大时,对请求进行限流,或者降级处理,返回默认值或错误信息,保护数据库。
public Object getDataWithRateLimit(String key) {
if (rateLimiter.allowRequest()) {
return getData(key);
} else {
return "Service is busy, please try again later.";
}
}
总结
缓存穿透、缓存击穿和缓存雪崩是缓存系统中常见的问题。通过使用布隆过滤器、互斥锁、双层缓存等技术,可以有效防止这些问题的发生,确保系统的稳定性和高可用性。选择合适的解决方案,可以显著提高系统的性能和用户体验。希望这些详细的解释和代码示例能帮助你更好地理解和解决这些缓存问题。