0
点赞
收藏
分享

微信扫一扫

【深入理解LRU Cache】:缓存算法的经典之作

目录

🧂1.引入缓存的优势

🥓2.哪些数据适合放入缓存 

🌭3.使用redis作为缓存组件 

🍿4.redis存在的问题 

🧈5.添加本地锁 

🥞6.添加分布式锁

🥚7.整合redisson作为分布式锁


🚗🚗🚗1.引入缓存的优势

🚗🚗🚗2.哪些数据适合放入缓存 

🚗🚗🚗3.使用redis作为缓存组件 

先确保reidis正常启动

3.1配置redis

  • 1.引入依赖
        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 2.配置reids信息
spring
  redis:
    port: 6379
    host: ip地址
    password: XXX

3.2优化查询 

之前都是从数据库查询的,现在加入缓存逻辑~

 /**
     * 使用redis缓存
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {

        //1.加入缓存,缓存中存的数据全都是json
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (StringUtils.isEmpty(catalogJson)) {
            //2.缓存中如果没有,再去数据库查找
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
            //3.将数据库查到的数据,将对象转换为json存放到缓存
            String s = JSON.toJSONString(catalogJsonFromDB);
            redisTemplate.opsForValue().set("catalogJson", s);
        }
        //4.从缓存中获取,转换为我们指定的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });

        return result;
    }

    /**
     * 从数据库查询并封装的分类数据
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {

        /**
         * 优化:将数据库查询的多次变为一次
         */

        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //1.查出所有1级分类
        List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);

        //2.封装数据
        Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个一级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            List<Catalog2Vo> catalog2Vos = null;
            if (categoryEntities != null) {
                catalog2Vos = categoryEntities.stream().map(l2 -> {
                    Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //找二级分类的三级分类
                    List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
                    if (categoryEntities3 != null) {
                        List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        vo.setCatalog3List(collect);
                    }
                    return vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));

        return parentCid;
    }

3.3测试 

在本地第一次查询后,查看redis,发现redis已经存储

使用JMeter压测一下

🚗🚗🚗4.redis存在的问题 

4.1缓存穿透

4.2缓存雪崩 

4.3缓存击穿 

🚗🚗🚗5.添加本地锁 

/**
     * 使用redis缓存
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        //1.使用redis缓存,存储为json对象
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        //2.判断缓存中是否有
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("缓存没有命中~查询数据库...");
            //3.如果缓存中没有,从数据库
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDB();
            return catalogJsonFromDB;
        }
        System.out.println("缓存命中....直接返回");
        //5.如果缓存中有,转换为我们需要的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;
    }


    /**
     * 从数据库查询并封装的分类数据
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDB() {

        /**
         * 优化:将数据库查询的多次变为一次
         */

        //TODO 本地锁,在分布式下,必须使用分布式锁
        //加锁,防止缓存击穿,使用同一把锁
        synchronized (this) {

            //加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库
            String catalogJson = redisTemplate.opsForValue().get("catalogJson");
            if (!StringUtils.isEmpty(catalogJson)) {
                //如果缓存中有,从缓存中获取
                Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
                });
                return result;
            }
            System.out.println("查询了数据库~");

            List<CategoryEntity> selectList = baseMapper.selectList(null);

            //1.查出所有1级分类
            List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);

            //2.封装数据
            Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                //1.每一个一级分类
                List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
                List<Catalog2Vo> catalog2Vos = null;
                if (categoryEntities != null) {
                    catalog2Vos = categoryEntities.stream().map(l2 -> {
                        Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                        //找二级分类的三级分类
                        List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
                        if (categoryEntities3 != null) {
                            List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
                                Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                return catalog3Vo;
                            }).collect(Collectors.toList());
                            vo.setCatalog3List(collect);
                        }
                        return vo;
                    }).collect(Collectors.toList());
                }
                return catalog2Vos;
            }));
            //4.将从数据库中获取的数据,转换为Json存储到redis
            String s = JSON.toJSONString(parentCid);
            //设置缓存时间,方式雪崩
            redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
            return parentCid;
        }
    }

🚗🚗🚗6.添加分布式锁

使用分布式锁 步骤

 /**
     * 使用redis缓存
     */
    @Override
    public Map<String, List<Catalog2Vo>> getCatalogJson() {
        //1.使用redis缓存,存储为json对象
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        //2.判断缓存中是否有
        if (StringUtils.isEmpty(catalogJson)) {
            System.out.println("缓存没有命中~查询数据库...");
            //3.如果缓存中没有,从数据库
            Map<String, List<Catalog2Vo>> catalogJsonFromDB = getCatalogJsonFromDBWithRedisLock();
            return catalogJsonFromDB;
        }
        System.out.println("缓存命中....直接返回");
        //5.如果缓存中有,转换为我们需要的类型
        Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
        });
        return result;
    }


    /**
     * 从数据库中获取数据,使用分布所锁
     *
     * @return
     */
    public Map<String, List<Catalog2Vo>> getCatalogJsonFromDBWithRedisLock() {
        //1.占分布式锁,去redis占位
        String uuid = UUID.randomUUID().toString();
        //2.设置过期时间,必须和加锁同步
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("获取分布式锁成功..." + redisTemplate.opsForValue().get("lock"));
            //加锁成功...执行业务
            Map<String, List<Catalog2Vo>> dataFromDb;
            try {
                dataFromDb = getDataFromDb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //删除锁,原子性
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return getDataFromDb();
        } else {
            //加锁失败...重试
            //休眠100毫秒
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {

            }
            return getCatalogJsonFromDBWithRedisLock();//自旋方式
        }
    }




    /**
     * 提起方法,从数据库中获取
     *
     * @return
     */
    private Map<String, List<Catalog2Vo>> getDataFromDb() {
        //加所以后,我们还要去缓存中确定一次,如果缓存中没有,才继续查数据库
        String catalogJson = redisTemplate.opsForValue().get("catalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            //如果缓存中有,从缓存中获取
            Map<String, List<Catalog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {
            });
            return result;
        }
        System.out.println("查询了数据库~");

        List<CategoryEntity> selectList = baseMapper.selectList(null);

        //1.查出所有1级分类
        List<CategoryEntity> leve1Categorys = getParent_cid(selectList, 0L);

        //2.封装数据
        Map<String, List<Catalog2Vo>> parentCid = leve1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            //1.每一个一级分类
            List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());
            List<Catalog2Vo> catalog2Vos = null;
            if (categoryEntities != null) {
                catalog2Vos = categoryEntities.stream().map(l2 -> {
                    Catalog2Vo vo = new Catalog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());
                    //找二级分类的三级分类
                    List<CategoryEntity> categoryEntities3 = getParent_cid(selectList, l2.getCatId());
                    if (categoryEntities3 != null) {
                        List<Catalog2Vo.Catalog3Vo> collect = categoryEntities3.stream().map(l3 -> {
                            Catalog2Vo.Catalog3Vo catalog3Vo = new Catalog2Vo.Catalog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3Vo;
                        }).collect(Collectors.toList());
                        vo.setCatalog3List(collect);
                    }
                    return vo;
                }).collect(Collectors.toList());
            }
            return catalog2Vos;
        }));
        //4.将从数据库中获取的数据,转换为Json存储到redis
        String s = JSON.toJSONString(parentCid);
        //设置缓存时间,方式雪崩
        redisTemplate.opsForValue().set("catalogJson", s, 1, TimeUnit.DAYS);
        return parentCid;
    }

🚗🚗🚗7.整合redisson作为分布式锁

7.1引入依赖

       <!--redisson作为所有分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>

7.2程序化配置

在配置地址时,一定要添加reds://

@Configuration
public class MyRedissonConfig {


    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() throws IOException {
        //1.创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.20.130:6379");
        //2.根据config创建redisson实例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

7.3实例解析

 @Autowired
 RedissonClient redissonClient;

 @ResponseBody
    @GetMapping("/hello")
    public String hello() {
        //1.设置redis的key获取一把锁,只要名字相同,就是同一把锁
        RLock lock = redissonClient.getLock("my-lock");
        //2.手动枷锁
        lock.lock();//阻塞式等待,默认30秒
        try {
            //3.执行业务
            System.out.println("枷锁成功!" + Thread.currentThread().getName());
            //4.模拟业务消耗时间
            Thread.sleep(20000);
        } catch (Exception e) {

        } finally {
            //3.释放锁
            System.out.println("释放锁~"+Thread.currentThread().getName());
            lock.unlock();//不删除,默认30秒后过期,自动删除
        }
        return "hello";
    }

7.4读写锁 

 @GetMapping("/write")
    @ResponseBody
    public String write() {

        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");
        String word = "";
        RLock rLock = readWriteLock.writeLock();
        try {
            //该数据加写锁,读数据加读锁
            rLock.lock();
            word = UUID.randomUUID().toString();
            Thread.sleep(3000);
            redisTemplate.opsForValue().set("writeValue", word);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        return word;
    }


    /**
     * 读写锁
     * @return
     */
    @GetMapping("/read")
    @ResponseBody
    public String read() {
        String word = "";
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWrite-lock");
        //加读锁
        RLock rLock = readWriteLock.readLock();
        rLock.lock();
        try {
            word = redisTemplate.opsForValue().get("writeValue");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            rLock.unlock();
        }
        return word;
    }
举报

相关推荐

0 条评论