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脚本来完成原子解锁
官方文档如下:

代码如下:

分布式锁还有更专业框架,在下面文章中有记载

posted @ 2023-01-31 21:05  不是孩子了  阅读(48)  评论(0)    收藏  举报