redis+redisson/setnx/synchronized具体用法

原创
小哥 3年前 (2022-11-09) 阅读数 4 #大杂烩

一、controller输入代码,获取目录信息。

    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map> getCatalogJson(){
        Map> catalogJson = categoryService.getCatalogJson();
        return catalogJson;
    }

二、service层

三、serviceImpl层

@Override
    public Map> getCatalogJson() {
        //注意:将其放入缓存中。json串,取出json字符串也会反转为可以使用的对象类型(即序列化和反序列化过程)。

        //1,加入缓存逻辑,缓存中存储的数据是json字符串。json优点是跨语言、跨平台的兼容性。
        String catalogJSON = stringRedisTemplate.opsForValue().get("catalogJSON");
        if (StringUtils.isEmpty(catalogJSON)) {
            //2如果不在缓存中,请检查数据库
            Map> catalogJsonFromDB = this.getCatalogJsonFromDBWithRedissonLock();//使用redisson分布式锁
            //Map> catalogJsonFromDB = this.getCatalogJsonFromDBWithRedisLock();//使用本机redis的setnx添加分布式锁
            //Map> catalogJsonFromDB = this.getCatalogJsonFromDBWithLocalLock();//在单个实例下使用synchronized加锁即可
            //3找到的数据在缓存中,转动找到的对象json放入缓存 --重要提示:这不是原子操作。应将其放入检查数据库的方法中,以便由锁控制。因此,下面的代码被注释掉,并放入检查数据库的方法中。
//            String s = JSON.toJSONString(catalogJsonFromDB);
//            stringRedisTemplate.opsForValue().set("catalogJSON",s);
//            return catalogJsonFromDB;
        }
        Map> result = JSON.parseObject(catalogJSON, new TypeReference>>() {
        });
        return result;

    }
//查询并封装数据库中的分类数据。 --本地synchronized锁在分布式情况下,多个实例将多次检查数据库,这可能导致多次刷新前台数据显示不一致。因此,在分布式情况下,您应该使用分布式锁。
    public Map> getCatalogJsonFromDBWithLocalLock() {
        synchronized (this) {
            return getDataFromDB();
        }
    }

    //上面方法的进化:查询并封装数据库中的分类数据。 --使用redis执行分布式锁
    public Map> getCatalogJsonFromDBWithRedisLock() {

        //1,占分布式锁的比例。.事实上,它是去redis占领坑,每个人都喜欢redis中set一个价值观,谁set如果你成功了,你就意味着谁成功地抓住了锁。获取锁并设置过期时间,以确保原子操作,因此在这里进行设置。
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
        if (lock) {

            //1,锁定成功(占坑) 可以执行业务

            //设置过期时间(在删除锁之前防止代码出现问题将导致锁保持未释放状态并成为死锁。) --请注意,锁定必须是一个原子操作,否则如果锁被锁定,然后程序将在设置到期时间之前崩溃,锁将永远不会被释放。
            //stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS); --这不是原子操作。如果它之前崩溃,它将不会再次设置过期时间和死锁。

            Map> dataFromDB = getDataFromDB();//为了防止出现问题,需要在上面设置锁的超市时间。

            //stringRedisTemplate.delete("lock");//该锁应在业务执行后删除,否则其他人将无法一直获得它。 如果业务执行时间过长并且锁过期,其他人将获得锁。如果在此处删除锁,其他人的锁将被删除。更多的人拿到了锁。因此,它应该改变uuid的方

            //获取价值比较+与成功删除相比,需要原子操作,因此下面的方法看起来是正确的,但也是错误的。 这需要使用redis+lua组合脚本以完成
            /*String lockValue = stringRedisTemplate.opsForValue().get("lock");
            if(uuid.equals(lockValue)){
                //我删除了自己的锁 --注意这里也是不对的,因为获取价值比较和删锁不是原子操作,也可能删除了别人的锁。
                stringRedisTemplate.delete("lock");
            }*/
            //使用lua脚本去解锁,保证了获取价值比较+成功删除比较必须是原子操作
            String lua = "if redis.call("get",KEYS[1]) == ARGV[1]

" + "then " + " return redis.call("del",KEYS[1]) " + "else " + " return 0 " + "end"; stringRedisTemplate.execute(new DefaultRedisScript(lua,Integer.class),Arrays.asList("lock"),uuid); return dataFromDB; } else { //2,未能锁定...需要重试(因为本地锁定synchronized这是一个旋转锁,一直在等待再次获取该锁。分布式锁redis还必须写入重试机制) //如果你放弃打电话太快,你可以使用睡眠。100ms,正在执行重试 return getCatalogJsonFromDBWithRedisLock();//转过身来,打电话给自己,然后继续尝试打开锁。

        }

    }

    //上面方法的再次进化:查询并封装数据库中的分类数据。 --使用redisson执行分布式锁
    public Map> getCatalogJsonFromDBWithRedissonLock() {
        RLock lock = redisson.getLock("CatalogJson-lock");
        lock.lock();
        Map> dataFromDB;
        try{
            dataFromDB = getDataFromDB();
        }finally{
            lock.unlock();
        }
        return dataFromDB;
    }

//查找数据库并将其放入缓存的方法。
private Map> getDataFromDB() {
        //查出所有1级分类
        List level1Categorys = this.getLevel1Categorys();

        //封装数据
        Map> parent_cid = level1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //每个初级分类,找到该初级分类的次级分类。
            List categoryEntities = baseMapper.selectList(new QueryWrapper().eq("parent_cid", v.getCatId()));
            //封装上述结果
            List category2Vos = null;
            if (categoryEntities != null) {
                category2Vos = categoryEntities.stream().map(l2 -> {
                    Category2Vo category2Vo = new Category2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //查找三级分类包的当前二级分类。vo
                    List level3Catelog = baseMapper.selectList(new QueryWrapper().eq("parent_cid", l2.getCatId()));
                    if (level3Catelog != null) {
                        List collect = level3Catelog.stream().map(l3 -> {
                            //以指定格式封装
                            Category2Vo.Catelog3Vo catelog3Vo = new Category2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catelog3Vo;
                        }).collect(Collectors.toList());
                        category2Vo.setCatelog3List(collect);
                    }
                    return category2Vo;
                }).collect(Collectors.toList());
            }
            return category2Vos;
        }));

        //3找到的数据在缓存中,转动找到的对象json放入缓存 --重要提示:这不是原子操作,应该放置getCatalogJsonFromDB方法,让它成为主题synic..管制
        String s = JSON.toJSONString(parent_cid);
        stringRedisTemplate.opsForValue().set("catalogJSON", s);
        return parent_cid;
    }
版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除