一、缓存雪崩
缓存雪崩:
redis挂掉了,请求全部走数据库
对缓存数据设置了相同的过期时间,导致缓存有段时间失效,请求全部走数据库
缓存雪崩,请求全部走数据库,数据库会挂掉,这样可能造成整个服务器坍塌
解决方案:
- 针对“对缓存数据设置了相同的过期时间,请求全部走数据库”
在缓存的时候给过期时间设置一个随机值,这样就会大幅的减少缓存过期时间在同一时间 - 分事发前、事发中、事发后的解决
事发前: 实现redis的高可用(利用redis cluster集群 或 主从+哨兵),减少redis挂掉
事发中: 万一redis真的挂掉了,可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们数据库挂掉
事发后:redis的持久化,重启redis后自动从磁盘上读取数,快速恢复缓存数据
二、缓存穿透
缓存穿透:
不停的访问一个数据库根本没有的数据,比如黑客在攻击数据库时会请求ID为负值的,但是数据库中不存在的数据时不会存到缓存中,这样就会导致数据库崩溃,服务宕机
解决方案:
- 当数据库查询没有数据时,将空对象设置到缓存中,并给此对象设置一个过期时间
- 在请求redis之前就做拦截,非法数据的拦截、过滤
三、缓存击穿
缓存击穿:
某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞
解决方案
- 将热点数据设置为永不过期
- 使用redis或者zookeeper的互斥锁,等一个请求构建万缓存之后再释放锁,进而其他请求才能访问此key( 一般使用此种方法来解决)
四、代码实现
1、防止缓存穿透源码实现
(1)使用的是Google的Bloom Filter
<1>引入依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
(2)使用双重验证锁解决高并发环境下的缓存穿透问题
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentMapper studentMapper;
//springboot自动配置的,直接注入到类中即可使用
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/**
* 查询所有学生信息,带有缓存
* @return
*/
public List<Student> getAllStudent() {
//在高并发条件下,会出现缓存穿透
List<Student> studentList = (List<Student>)redisTemplate.opsForValue().get("allStudent");
if (null == studentList) {
//5个人, 4个等,1个进入
synchronized (this) {
//双重检测锁,假使同时有5个请求进入了上一个if(null == studentList),
//加了锁之后one by one 的访问,这里再次对缓存进行检测,尽一切可能防止缓存穿透的产生,但是性能会有所损失
studentList = (List<Student>)redisTemplate.opsForValue().get("allStudent");
if (null == studentList) {
studentList = studentMapper.getAllStudent();
redisTemplate.opsForValue().set("allStudent", studentList);System.out.println("请求的数据库。。。。。。");} else {//System.out.println("请求的缓存。。。。。。");
}}} else {System.out.println("请求的缓存。。。。。。");
}return studentList;}}
2、防止缓存雪崩、缓存击穿源码实现
(1)加互斥锁,互斥锁参考代码如下:
static Lock reenLock = new ReentrantLock();
public List<String> getData04() throws InterruptedException {
List<String> result = new ArrayList<String>();
// 从缓存读取数据
result = getDataFromCache();
if (result.isEmpty()) {
if (reenLock.tryLock()) {
try {
System.out.println("我拿到锁了,从DB获取数据库后写入缓存");
// 从数据库查询数据
result = getDataFromDB();
// 将查询到的数据写入缓存
setDataToCache(result);
} finally {
reenLock.unlock();// 释放锁
}
} else {
result = getDataFromCache();// 先查一下缓存
if (result.isEmpty()) {
System.out.println("我没拿到锁,缓存也没数据,先小憩一下");
Thread.sleep(100);// 小憩一会儿
return getData04();// 重试
}
}
}
return result;
}
3、缓存穿透、雪崩、击穿最终结果都是对数据库造成压力
(1)不管是使用双重检索还是加互斥锁,都是为了减轻DB压力
(2)一般情况下使用加互斥锁来实现