缓存穿透
定义
当我们请求去查询一条记录,先到redis中查询后到mysql查询都发现找不到该条记录,但是请求每次都会打到数据库上面去,导致后台数据库压力暴增,这些请求像“穿透”了缓存一样直接打在数据库上,这种现象就叫做缓存穿透。这种现象我们称为缓存穿透,这个redis变成了一个摆设。
恶意对网站进行攻击时,拿一个不存在的id去查询数据,会产生大量的请求到数据库去查询。可能会导致你的数据库由于压力过大而宕掉
解决方案
方案1:空对象缓存或者缺省值
一旦发生缓存穿透,我们就可以针对查询的数据,在Redis中缓存一个空值或是和业务层协商确定的缺省值(例如,库存的缺省值可以设为0) 。紧接着,应用发送的后续请求再进行查询时,就可以直接从Redis中读取空值或缺省值,返回给业务应用了,避免了把大量请求发送给数据库处理,保持了数据库的正常运行。
如果Redis查不到数据,数据库也查不到,我们把这个Key值保存进Redis,设置value="null",当下次再通过这个Key查询时就不需要再查询数据库。这种处理方式肯定是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。而且随着缓存的暑假的越来越多,对Redis的压力越来越大,可以对缓存的Key有个过期的时间分担压力。
方案2:布隆过滤器
- 把已存在数据的key存在布隆过滤器中,相当于redis前面挡着一个布隆过滤器。
- 当有新的请求时,先到布隆过滤器中查询是否存在:
- 如果布隆过滤器中不存在该条数据则直接返回;
- 如果布隆过滤器中已存在,才去查询缓存redis,如果redis里没查询到则再查询Mysql数据库
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。
一句话就是:由一个初始值为零的bit数组和多个哈希函数构成,用来快速判断集合中是否存在某个元素。
缓存击穿
定义
大量的请求同时查询一个key时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去(简单说就是热点key突然失效了,暴打mysql)。
造成缓存击穿的主要原因就是:我们为缓存中的数据设置了过期时间。如果在某个时刻从数据库获取了大量的数据,并设置了相同的过期时间,这些缓存的数据就会在同一时刻失效,造成缓存击穿问题
- 时间到了自然清除,但还被访问到了
- 删除key的时候,突然被访问到了
解决方案
方案1:热点数据永不过期
对于热点数据,如果业务允许的话,对于热点的key可以设置永不过期的key。
方案2:互斥独占锁防止击穿
使用分布式锁思想,保证对于每个 Key 同时只有一个线程去查询后端的服务,某个线程在查询后端服务的同时,其他线程没有获得分布式锁的权限,需要进行等待,查询到以后回写Redis,其他请求线程再到Redis中查询,当然这样会导致系统的性能变差。
示例java代码如下:
public User findUserById2(Integer id) { User user = null; String key = CACHE_KEY_USER+id; //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql user = (User) redisTemplate.opsForValue().get(key); if(user == null) { //2 大厂用,对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql synchronized (UserService.class){ user = (User) redisTemplate.opsForValue().get(key); //3 二次查redis还是null,可以去查mysql了(mysql默认有数据) if (user == null) { //4 查询mysql拿数据 user = userMapper.selectByPrimaryKey(id);//mysql有数据默认 if (user == null) { return null; }else{ //5 mysql里面有数据的,需要回写redis,完成数据一致性的同步工作 //setnx 不存在才创建 redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS); } } } } return user; }
缓存雪崩
定义
缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库,还有一种情况是Redis主机挂了,Redis全盘崩溃
在某一时刻缓存集中失效,或者缓存系统出现故障,所有的并发流量就会直接到达数据库。数据存储层的调用量就会暴增,用不了多长时间,数据库就会被大流量压垮
解决方案
方案1:过期时间设置随机
在原有的失效时间上加上一个随机值,比如1-5分钟随机。这样就避免了因为采用相同的过期时间导致的缓存雪崩。
方案2:缓存预热
缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓存
方案3:Redis集群
为了防止Redis宕机导致缓存雪崩的问题,可以搭建Redis集群,提高Redis的容灾性。