缓存穿透:
查询了不存在的数据 。这样的数据 因为redis中找不到,会查询数据库,量大可能造成数据库压力过大而崩溃。
问题与解决思路:
避免经常查询数据库。
解决办法1:
在查询数据库后,无论是否为空都加到redis中(正常情况空的数据不会加到redis中),这样下次再请求这个数据就不需要再查询数据库了。
需要注意:
会多很多key。
如果在redis中已经指定了某个key的值为空,后在数据库中添加了这个值时也要更新redis中的值,避免数据不统一的问题。
解决办法2:
使用布隆过滤器(你不可以永远相信布隆!)
程序启动时(大部分是) 创建布隆过滤器,这个过程叫缓存预热。
在去redis查询某个key之前,先在布隆过滤器中查询是否存在,如果存在则继续查询redis,redis中没有则去数据库查询。如果没有则直接返回,不去redis或数据库中查询。
需要注意:
布隆过滤器可能误判!
缓存击穿:
是指一个已经存在的key在redis中过期失效时,请求都绕过缓存直接查询后端数据库。量大可能造成数据库崩溃。
问题与解决思路:
避免经常查询数据库。
解决办法1:
数据如果设置不会过期就不存在这个问题。
解决办法2:
使用互斥锁:在缓存失效时,使用分布式锁来确保只有一个线程去加载数据并回设到缓存中,其他线程等待并从缓存获取数据。这样可以避免多个线程同时从数据库加载相同数据。
两种示例代码
public class Cache {
private Map<String, Object> cacheMap = new HashMap<>();
private Lock lock = new ReentrantLock();
public Object getData(String key) {
Object data = cacheMap.get(key);
if (data == null) {
// 缓存失效,需要重新加载数据
lock.lock(); // 获取互斥锁
try {
// 再次检查缓存,可能其他线程已经加载了数据
data = cacheMap.get(key);
if (data == null) {
// 加载数据
data = loadDataFromDatabase(key);
cacheMap.put(key, data);
}
} finally {
lock.unlock(); // 释放互斥锁
}
}
return data;
}
private Object loadDataFromDatabase(String key) {
// 从数据库加载数据的逻辑
return null;
}
}
public class Cache {
private Map<String, Object> cacheMap = new HashMap<>();
public Object getData(String key) {
Object data = cacheMap.get(key);
if (data == null) {
synchronized (this) {
// 再次检查缓存,可能其他线程已经加载了数据
data = cacheMap.get(key);
if (data == null) {
// 加载数据
data = loadDataFromDatabase(key);
cacheMap.put(key, data);
}
}
}
return data;
}
}