文章目录
前言
缓存穿透是指,查询数据库中的一个一定不存在的数据,查询流程是先对redis进行查询,缓存未命中,在对数据库进行查询,假如存在10w个不存在的数据查询请求,这十万个请求将全部打到数据库上面。
一、解决方案一
对不存在的数据,我们也进行缓存,这个数据是一个固定的值,比如x,每个查询到这个固定的值x,直接返回从而减小数据库的压力。
参考代码
public List<CategoryVo> findCatgorysTree() {
String catgorysJsonStr = redisTemplate.opsForValue().get(GmallConstant.REDIS_CACHE_CATEGORY_KEY);
//判断缓存之中是否有数据
if (!StringUtils.isEmpty(catgorysJsonStr)) {
//存在
//判断这个值是否是x,
if ("x".equals(catgorysJsonStr)) {
//是x,
log.info("数据库中不存在的值,redis返回null......");
//返回null
return null;
} else {
//不是x,是说明是真是存在的数据
//返回
return JSON.parseArray(catgorysJsonStr, CategoryVo.class);
}
} else {//缓存之中没有数据
//查询数据库
List<CategoryVo> categoryVoList = baseCategory1Mapper.findCatgorysTree();
//是否查出这个数据
if (categoryVoList != null && categoryVoList.size() != 0) {
log.info("数据库中存在这个值.........");
//数据库存在这个值
String toJSONString = JSON.toJSONString(categoryVoList);
//返回redis中在返回
redisTemplate.opsForValue().set(GmallConstant.REDIS_CACHE_CATEGORY_KEY, toJSONString);
return categoryVoList;
} else {
log.info("数据中不存在这个值.........");
//数据库中不存在这个值
redisTemplate.opsForValue().set(GmallConstant.REDIS_CACHE_CATEGORY_KEY, "x");
return categoryVoList;
}
}
}
存在的弊端:无法解决随机值穿透的问题,其他值还是会请求数据库,并且每一次请求都会在redis中进行存储一个值 x,对于redis的内存消耗比较严重。
二、解决方案二(方案一优化)
缓存预热:服务启动时就将数据从数据库缓存到redis中,在数据查询的时候以缓存中的数据为准,缓存中如果存在数据,直接返回,不存在返回null。
存在的弊端:消耗redis中的内存,缓存一致性问题。
三、解决方案三(使用过滤器的思想)
开辟两个内存空间,存储不同的内容.
三、解决方案四(对解决方案三改进,减少skuId的内存消耗):布隆过滤器
- 布隆过滤器作用:判断一个数据是否存在
- 布隆过滤器的组成:二级制数组 + hash函数
- 布隆过滤器存储数据的过程:对要存储的数据进行hash计算,将对应结算结果作为数组的索引,并将对应索引上的值更改为1(默认为0)
- 布隆过滤器如何判断数据是否存在:对需要判断的数据进行hash计算,判断对应索引上的值是否全部为1,如果为1,则可能存在,如果存在不为1的,则一定不存在
- 布隆过滤器不支持删除。
入门案例
package com.atguigu.gmall;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
public class BloomFilterTest {
public static void main(String[] args) {
//初始化一个布隆过滤器
/**
* Funnel<? super T> funnel, 限定布隆过滤器中存储的元素类型
* int expectedInsertions, 期望布隆过滤器中存储的元素个数
* double fpp, 误判率,越低,数组越大
*/
BloomFilter<Long> longBloomFilter = BloomFilter.create(Funnels.longFunnel(), 100000, 0.0000001);
//向布隆过滤器中添加数据
longBloomFilter.put(45l);
longBloomFilter.put(50l);
longBloomFilter.put(53l);
//判断数据在布隆过滤器中是否存在
System.out.println(longBloomFilter.mightContain(45l));
System.out.println(longBloomFilter.mightContain(50l));
System.out.println(longBloomFilter.mightContain(100l));
}
}
在解决方案二中服务启动时就将数据从数据库缓存到redis中,如何去做?
spring的生命周期:
- 通过实现InitializingBean接口继承afterPropertiesSet()方法
- 自定义init()方法,加上@PostConstruct注解.