读多写少用缓存,写多读少用队列。
查询缓存,查不到取数据库,放入缓存。
public String query(){
// 从缓存中获取数据
String key = "key";
String value = redisService.get(key);//高并发场景下,所有线程进来查询缓存,缓存中没有数据时,来不及将数据库中数据放入缓存,全部去查询数据库
if(value != null){
System.out.println("从缓存中获取数据!");
return value;
}
// 从数据库中获取数据
value = dataService.get(key);
System.out.println("从数据库中获取数据!");
// 将数据插入缓存
redisService.set(key, value, 5*60);
return value;
}
一、缓存失效
1、高峰期大面积缓存key失效 / 缓存挂掉
解决:设置不同key不同失效时间,根据业务场景设置,或采用随机数设置。避免相同失效时间,导致一时大量数据失效,全部查询数据库。
2、局部高峰期,热点缓存key失效
导致热点数据获取海量请求直击数据库。
二、缓存雪崩
因缓存服务挂掉或者热点缓存失效,导致大量请求查询数据库,数据库服务器压力过大,数据库连接不够用或者数据库处理不过来,导致整个系统不可用。依赖数据库的其他系统面临崩溃风险。
解决:
增加互斥锁,拿到锁的线程查询数据库,写入缓存,其他线程判断缓存是否存在,此时之前拿到锁的线程已经把数据写入了,缓存存在,不在查询数据库。
synchronized:同步关键字,导致线程一直等待锁,全部请求排队阻塞影响效率。
public String query(){
// 从缓存中获取数据
String key = "key";
String value = redisService.get(key);
if(value != null){
System.out.println("从缓存中获取数据!");
return value;
}
synchronized (this) {
// 从数据库中获取数据
value = dataService.get(key);
System.out.println("从数据库中获取数据!");
// 将数据插入缓存
redisService.set(key, value, 5*60);
return value;
}
}
lock:灵活获取锁,释放锁。
lock(); //获取锁,多个线程争抢锁,抢不到一直等待,阻塞,不可中断。
lockInterruptibly(); //获取锁,等待,阻塞,可中断。
tryLock(); //尝试获取锁,取不到返回false
tryLock(long l, TimeUnit timeunit); //指定时间内尝试获取锁,取不到返回false
unlock(); //释放锁
Lock lock = new ReentrantLock();
public String query(){
// 1.从缓存中获取数据
String key = "key";
String value = redisService.get(key);
if(value != null){
System.out.println("从缓存中获取数据!");
return value;
}
lock.lock();// 所有线程排队获取锁,只有1个能拿到
try {
value = redisService.get(key);// 二次校验
if(value != null){
System.out.println("从缓存中获取数据!");
return value;
}
// 2.从数据库中获取数据
value = dataService.get(key);
System.out.println("从数据库中获取数据!");
// 3.将数据插入缓存
redisService.set(key, value, 5*60);
return value;
} finally{
lock.unlock();
}
}
优点:简单有效,请求不会全部涌向数据库;适用范围广
缺点:锁会阻塞所有线程,排队获取锁,加大等待时长,用户体验差; 锁的颗粒度粗,不同的key被同一把锁阻塞(针对不同key单独加锁)
针对不同key单独加锁:
@Autowired
RedisService redisService;
@Autowired
DataService dataService;
ConcurrentHashMap<String, String> mapLock = new ConcurrentHashMap<String, String>();//map记录锁状态
public String query(){
// 1.从缓存中获取数据
String key = "key";
String value = redisService.get(key);
if(value != null){
System.out.println("从缓存中获取数据!");
return value;
}
boolean lock = false;
try {
// putIfAbsent 等同于redis setnx,存在则返回,不存在则插入
lock = mapLock.putIfAbsent(key, key) == null;//插入一条数据,判断如果不存在则插入并返回null,存在则获取旧值返回。如果返回null,意味着插入成功,获得锁
if (lock) {//拿到锁
value = redisService.get(key);// 二次校验
if(value != null){
System.out.println("从缓存中获取数据!");
return value;
}
// 2.从数据库中获取数据
value = dataService.get(key);
System.out.println("从数据库中获取数据!");
// 3.将数据插入缓存
redisService.set(key, value, 5*60);
}else {// 没拿到锁怎么办?--缓存降级
// 1、重试,争抢锁,控制重试次数
// 2、返回空
// 3、备用缓存
value = null;
System.out.println("缓存降级!");
}
return value;
} finally{
if (lock) {
mapLock.remove(key);//释放锁
}
}
}
优点:减小锁的颗粒度
缺点:锁的颗粒度仍然大,同一个key,只有一个请求能获得锁,其余全部阻塞。