缓存击穿 本地锁的解决
产生缓存击穿的原因
发生缓存击穿的原因是在高并发场景下 大量请求访问一个已失效数据 频繁访问数据库 导致数据库宕机
解决方案
可以引入本地锁
因为在springboot 容器中的bean都是单例 所以只要锁住当前对象即可
在进行缓存查询时 若发现在缓存中数据不存在 去数据库中查询 在数据库查询时添加一把互斥锁 只有拿到锁才能进行查询
在进行数据库查询前 先判断缓存中是否有数据 如果有数据就不用在进行查询了 否则在进行查询时只是将线程阻塞掉了 当释放锁后 还会再次查询数据库
查询后的结果保存到缓存中 然后释放锁 当下一个请求进行访问缓存时, 此时缓存中已有数据 就不会出现缓存击穿的问题
本地锁的业务实现
/*
* 加锁业务流程:
* 1. 判断redis是否有缓存数据
* 2. 若有缓存数据 直接反序列化对象并进行返回
* 若缓存中无数据 则进行数据库查询 并将缓存结果保存至redis中
* */
public Map<String, List<Catalog2VO>> getCatalogJsonResult() throws JsonProcessingException {
/*
* 加锁流程:
* 1. 判断redis是否有缓存数据
* 2. 若有缓存数据 直接反序列化对象并进行返回
* 若缓存中无数据 则进行数据库查询 并将缓存结果保存至redis中
* */
// 因为在springboot 容器中的bean都是单例 所以只要锁住当前对象即可
synchronized (this) {
String categoryCache = redisTemplate.opsForValue().get(REDIS_CACHE_CATEGORY);
if (StringUtils.isEmpty(categoryCache)) {
System.out.println("数据库查询");
List<PmsCategory> categories = categoryMapper.selectList(null);
// 查询一级分类
List<PmsCategory> categoryByFirstCategory = getParentCid(categories, 0L);
// 数据库查询业务逻辑
Map<String, List<Catalog2VO>> categoryMap = categoryByFirstCategory.stream().collect(Collectors.toMap((item) -> item.getCatId().toString(), (item) -> {
List<PmsCategory> category2List = getParentCid(categories, item.getCatId());
// 根据一级分类id 查询二级分类
return category2List.stream().map((category2) -> {
Catalog2VO catalog2VO = new Catalog2VO();
catalog2VO.setCatalog1Id(item.getCatId().toString());
catalog2VO.setId(category2.getCatId().toString());
catalog2VO.setName(category2.getName());
List<PmsCategory> category3List = getParentCid(categories, category2.getCatId());
// 根据二级分类id 查询三级分类
List<Catalog2VO.Catalog3> catalog3s = category3List.stream().map((category3) -> {
Catalog2VO.Catalog3 catalog3 = new Catalog2VO.Catalog3();
catalog3.setCatalog2Id(category3.getParentCid().toString());
catalog3.setId(category3.getCatId().toString());
catalog3.setName(category3.getName());
return catalog3;
}).collect(Collectors.toList());
catalog2VO.setCatalog3List(catalog3s);
return catalog2VO;
}).collect(Collectors.toList());
}));
// 将缓存存入redis中
redisTemplate.opsForValue().set(REDIS_CACHE_CATEGORY, objectMapper.writeValueAsString(categoryMap));
return categoryMap;
} else {
// 若缓存存在 则直接反序列对象并进行返回
return objectMapper.readValue(categoryCache, new TypeReference<Map<String, List<Catalog2VO>>>() {});
}
}
}
本地锁的不足
本地锁只能锁住当前进程 在分布式场景下 每个服务有多个实例 而每个实例又是不同的进程 导致锁不住所有服务 会读取多次数据库 这时候就需要分布式锁了
虽然道路是曲折的,但前途是光明的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了