【高并发下Redis可能存在的问题及解决方案】
一、缓存穿透(面试问题)
在实际开发中,添加缓存工具的目的,减少对数据库的访问次数,增加访问效率。
肯定会出现Redis中不存在的缓存数据。例如:访问id=-1的数据。可能出现绕过redis依然频繁访问数据库的情况,称为缓存穿透,多出现在查询为null的情况不被缓存时。
解决办法:
如果查询出来为null数据,把null数据依然放入到redis缓存中,同时设置这个key的有效时间比正常有效时间更短一些。
if(list==null){
// key value 有效时间 时间单位
redisTemplate.opsForValue().set(navKey,null,10, TimeUnit.MINUTES);
}else{
redisTemplate.opsForValue().set(navKey,result,7,TimeUnit.DAYS);
}
二、缓存击穿(面试问题)
实际开发中,考虑redis所在服务器中内存压力,都会设置key的有效时间。一定会出现键值对过期的情况。如果正好key过期了,此时出现大量并发访问,这些访问都会去访问数据库,这种情况称为缓存击穿。
解决办法:永久数据。
加锁:防止出现数据库的并发访问。
1. ReentrantLock(重入锁)
JDK对对于并发访问处理的内容都放入了java.util.concurrent中
ReentrantLock的性能和synchronized是没有区别的,但是API使用起来更加方便。
@SpringBootTest
public class MyTest {
@Test
public void test(){
new Thread(){
@Override
public void run() {
test2("第一个线程111111");
}
}.start();
new Thread(){
@Override
public void run() {
test2("第二个线程222222");
}
}.start();
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ReentrantLock lock = new ReentrantLock();
public void test2(String who){
lock.lock();
if(lock.isLocked()) {
System.out.println("开始执行:" + who);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完:" + who);
lock.unlock();
}
}
}
2. 解决缓存击穿实例代码
只有在第一次访问时和Key过期时才会访问数据库。对于性能来说没有过大影响,因为平时都是直接访问redis。
private ReentrantLock lock = new ReentrantLock();
@Override
public Item selectByid(Integer id) {
String key = "item:"+id;
if(redisTemplate.hasKey(key)){
return (Item) redisTemplate.opsForValue().get(key);
}
lock.lock();
if(lock.isLocked()) {
Item item = itemDubboService.selectById(id);
// 由于设置了有效时间,就可能出现缓存击穿问题
redisTemplate.opsForValue().set(key, item, 7, TimeUnit.DAYS);
lock.unlock();
return item;
}
// 如果加锁失败,为了保护数据库,直接返回null
return null;
}
三、缓存雪崩(面试问题)
在一段时间内,出现大量缓存数据失效,这段时间内数据库的访问频率骤增,这种情况称为缓存雪崩。
解决办法:永久生效。
自定义算法,例如:随机有效时间。让所有key尽量避开同一时间段。
int seconds = random.nextInt(10000);
redisTemplate.opsForValue().set(key, item, 100+ seconds, TimeUnit.SECONDS);