70、缓存---分布式锁---分布式锁的原理及使用
阶段一
代码如下:
private Map<String, List<CatelogTwoLevelVo>> getCatalogJsonFromDb() { //得到锁之后,去缓存中再确定一次是否有数据 String catalogJson = redisTemplate.opsForValue().get("catalogJson"); if (!StringUtils.isEmpty(catalogJson)) {//缓存中有数据 Map<String, List<CatelogTwoLevelVo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CatelogTwoLevelVo>>>() { }); return result; } //查询所有的一级分类 List<CategoryEntity> categoryEntities = this.selectOneLevelCategory(); //查询所有的二级分类和三级分类并封装数据 //map的key是一级分类的id,v是二级分类的vo Map<String, List<CatelogTwoLevelVo>> map = categoryEntities.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> { //查询每一个一级分类下的二级分类 List<CategoryEntity> categoryTwoLevelEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId())); List<CatelogTwoLevelVo> collect = null; if (categoryTwoLevelEntities != null) { collect = categoryTwoLevelEntities.stream().map(l2 -> { //查询该二级分类对应的三级分类并封装数据 List<CategoryEntity> categoryThreeLevelEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId())); List<CatelogTwoLevelVo.CatalogThreeLevelVo> catalogThreeLevelVos = null; if (categoryThreeLevelEntities != null) { catalogThreeLevelVos = categoryThreeLevelEntities.stream().map(l3 -> { CatelogTwoLevelVo.CatalogThreeLevelVo catalogThreeLevelVo = new CatelogTwoLevelVo.CatalogThreeLevelVo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName()); return catalogThreeLevelVo; }).collect(Collectors.toList()); } //构造二级分类的vo CatelogTwoLevelVo catelogTwoLevelVo = new CatelogTwoLevelVo(v.getCatId().toString(), catalogThreeLevelVos, l2.getCatId().toString(), l2.getName()); return catelogTwoLevelVo; }).collect(Collectors.toList()); } return collect; })); String s = JSON.toJSONString(map); int minutes = 1440 + new Random().nextInt(10);//过期时间:1天(1440分钟)加上随机数 redisTemplate.opsForValue().set("catalogJson", s, minutes, TimeUnit.MINUTES);//存入缓存 return map; }
但是存在问题:如果一个线程在redis中站好了坑位,但是业务代码执行出现异常,有可能不会解锁,这就可能导致其余线程都没法获得锁,也就获取不到数据。即使我们处理异常,在finally中进行解锁,如果发生停电宕机,也会出现解锁不成功的情况
解决办法:给锁添加过期时间
阶段二


但是存在问题:在我们设置过期时间之前突然宕机,又会陷入死循环,没有线程能获取锁
因此加锁和设置过期时间必须是一个原子操作;
阶段三


但是存在问题:如果我们的业务代码执行时间比较长,在我们删除锁之前,我们之前设置的锁已经自己过期了。现在redis中的锁是别的线程的锁,这样我们就删除了别人的锁。因此使用uuid区分每个线程的锁
阶段四


但是存在问题:我们删除锁是先获取值进行对比,然后对比成功才删除锁。如果我们对比成功了,恰好进入了if,但是此时我们的锁正好到时间过期了,别的进行又立刻占了一个锁,这时我们删除的锁就不是自己的锁。就会导致有一个线程抢到锁,即没锁住。因此,获取值+删除锁应该是一个原子操作
阶段五:最终形态
使用lua脚本来完成原子解锁
官方文档如下:

代码如下:

分布式锁还有更专业框架,在下面文章中有记载
分类:
谷粒商城
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律