缓存穿透、缓存雪崩、缓存击穿、缓存污染
缓存 锁 面试 About 2,872 words缓存穿透
场景描述
缓存和数据库中都没有的数据,用户不断发起请求。如发起id=-1
等不合理数据,导致数据库压力过大。
解决方法
- 增加传参校验,如
Validation
中的@NotNull
、@MinValue
等。 - 从数据库中取不到数据,则在缓存层设置
value=null
的键值对,并设置一个过期时间,比如30
秒。可防止反复请求无效数据的请求。
缓存雪崩
场景描述
缓存中数据大批量过期,不同关键字的查询都直接到数据库层,导致数据库压力过大甚至宕机。
解决方法
- 热点数据设置永不过期。
- 缓存数据过期时间分散设置,防止同一时间大量数据过期。
- 将热点数据均匀分布在不同
Cluster
上。
缓存击穿
也叫缓存踩踏。
场景描述
缓存中数据过期,从数据库中读取,由于并发用户特别多,同时读缓存都没有,请求全部打到数据库,导致数据库压力过大。
解决方法
- 设置热点数据永不过期。
- 添加互斥锁,一般并发量大都采用分布式锁。
互斥锁代码
可使用递归或自旋,等待一定时间再次获取,若超过指定阈值则返回为空。
getData()
为递归实现,getData2()
为自旋实现,示例代码采用Java Lock
实现,真实分布式场景替换为分布式锁即可。
@Component
public class CacheUtil {
private static ConcurrentHashMap<String, String> CACHE = new ConcurrentHashMap<>();
private static final ReentrantLock lock = new ReentrantLock();
public String getData(String keywords) throws InterruptedException {
String data = getDataFromCache(keywords);
if (data == null) {
if (lock.tryLock()) {
try {
data = getDataFromDB(keywords);
saveToCache(keywords, data);
} finally {
lock.unlock();
}
} else {
TimeUnit.SECONDS.sleep(300);
data = getData(keywords);
}
}
return data;
}
public String getData2(String keywords) throws InterruptedException {
String data = getDataFromCache(keywords);
if (data == null) {
int count = 0;
while (!lock.tryLock()) {
TimeUnit.SECONDS.sleep(300);
if (++count >= 5) {
return null;
}
}
try {
data = getDataFromCache(keywords);
if (data == null) {
data = getDataFromDB(keywords);
saveToCache(keywords, data);
}
} finally {
lock.unlock();
}
}
return data;
}
private String getDataFromCache(String keywords) {
return CACHE.get(keywords);
}
private String getDataFromDB(String keywords) throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return null;
}
private void saveToCache(String keywords, String data) {
CACHE.put(keywords, data);
}
}
自旋和 sleep 问题
自旋可能导致CPU
飙升,而引入线程sleep
后,可能导致惊群效应,而且也存在不同线程重复请求资源的可能。可使用FutureTask
解决,下一篇具体讲FutureTask
解决缓存击穿(缓存踩踏)问题。
缓存污染
场景描述
有些数据访问次数非常少,可能访问一次就再也不访问了,一直滞留在缓存中,没有应用使用,这样的key
多了就可能会占据大部分的缓存空间。
解决方法
- 方法一:设置过期时间。
- 方法二:设置
volatile-ttl
缓存淘汰策略,当内存满时,选择淘汰剩余存活时间最短的key
。 - 方法三:设置
volatile-lru
缓存淘汰策略,当内存满时,选择淘汰设置了过期时间的key
中最近最少使用的key
。 - 方法四:设置
allkeys-lru
缓存淘汰策略,当内存满时,选择淘汰最近最少使用的key
(全部key
参与lru
淘汰)。 - 方法五:
Redis 4.0
后设置volatile-lfu
缓存淘汰策略,当内存满时,选择淘汰设置了过期时间的key
中最不经常使用的key
。 - 方法六:
Redis 4.0
后设置allkeys-lfu
缓存淘汰策略,当内存满时,选择淘汰最不经常使用的key
(全部key
参与lfu
淘汰)。
备注
获取热点数据,可使用Redis
中的redis-cli --hotkeys
命令查看。
Views: 2,958 · Posted: 2021-03-22
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓
Loading...