文章目录
一、什么是缓存穿透?
高并发场景下,如果某一个 key 被高并发访问,没有被命中,会尝试去从后端数据库中获取,这就导致了大量请求到达数据库,数据库可能因为扛不住压力而挂掉。
二、解决思路
通过加锁的方式,只允许一个请求访问数据库(Mysql),其他没抢到锁的请求返回空或者失败失败。
二、解决方案
几种方案的一步步分析改进:Atomic,ReentrantLock,ConcurrentHashMap,Guava Striped,Redisson
1. Java同步器
比如:synchronized
,reentrantLock
,AtomicBoolean
等
代码如下:
AtomicBoolean atomicBoolean = new AtomicBoolean(false);
public Integer testCacheLock(String bookId) {
Integer result = (Integer) redisUtil.get(EnumBookRedisKey.UPDATE_BOOK_DAYS, bookId);
//缓存不存在
if (result == null) {
//抢锁
if (!atomicBoolean.compareAndSet(false, true)) {
//没抢到锁
return null;
}
//抢到锁
try {
log.info("模拟查库");
result = 100;
//写缓存
redisUtil.set(EnumBookRedisKey.UPDATE_BOOK_DAYS, bookId, result);
} finally {
atomicBoolean.set(false);
}
}
log.info("模拟后续业务对result进行处理:{}", result);
return result;
}
优点:简单。Java层面的同步器,不需要引入第三方组件。
缺点:不能基于key控制锁的粒度,导致一个bookId失效,所有bookId都返回失败。
2. ConcurrentHashMap
通过map来存放key和对应的锁对象,可以实现基于key的加锁。
代码如下:
private Map<String, AtomicBoolean> locksMap = new ConcurrentHashMap<>();
public Integer testCacheLock(String bookId) {
Integer result = (Integer) redisUtil.get(EnumBookRedisKey.UPDATE_BOOK_DAYS, bookId)