Redis学习之缓存

本文介绍了缓存的概念,重点讲解了如何在项目中使用Redis作为缓存,以及主动更新策略(包括方法一:同步更新数据库和缓存)、缓存遭到的三种攻击(缓存穿透、雪崩、缓存击穿)及其解决思路,如互斥锁和逻辑过期的使用。
摘要由CSDN通过智能技术生成

目录

什么是缓存

添加Redis缓存

缓存更新策略

   主动更新策略

方法一

1、更新数据

2、保证数据库和缓存的操作同时成功或失败。

方法三

缓存遭到的三种攻击

1、缓存穿透

解决思路 

2、雪崩

解决思路 

3、缓存击穿

解决思路


什么是缓存

简单来讲就是,数据交换的缓冲区,是存储数据的临时地方,读写性能较高。

添加Redis缓存

作用模型--流程。应当把每次未命中的数据及时写入Redis缓存。

public Result queryById1(Long id) {
        String key = CACHE_SHOP_KEY + id;
        //1、从redis中查
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2、判断是否存在
        if(shopJson != null){
            //3、存在,直接返回
            Shop shop  = JSONUtil.toBean(shopJson,Shop.class);
            return Result.ok(shop);
        }
        //4、不存在,查询数据库
        Shop shop = getById(id);
        if(shop == null){
            //5、数据库不存在,返回错误
            return Result.fail("数据错误!");
        }
        //6、数据库存在,写入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //7、返回
        return Result.ok(shop);
    }

缓存更新策略

缓存更新策略,有三种。

   主动更新策略

   目前软件系统采取的都是主动更新策略,能够对数据及时更新,保持一致。主动更新策略中有不同的更新方式。

方法一

   在实际开发中,缓存更新策略,还是采取的方法一:由缓存的调用者,在更新缓存的同时,将数据写入数据库。 

1、更新数据

采用删除缓存,再写入的策略。避免无效数据的更新。

2、保证数据库和缓存的操作同时成功或失败。

 在单体系统中,将操作放在一个事务中。

 在分布式系统中,利用TCC等分布式方案。

3、操作顺序

先操作数据库,再更新缓存。理由如下:

        先删除缓存,后续访问的对象,获取到的数据都是旧数据,且因为是写入缓存的操作快于更新数据库操作。而且访问缓存的速度极快,这会导致很多对象获取的数据是缓存中的旧数据。发生概率极大。 

@Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        // 1.更新数据库
        updateById(shop);
        // 2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return Result.ok();
    }

方法三

在Redis中又有两种不同方式的持久化策略,这里简单说一下。分别是AOFRDB

  

缓存遭到的三种攻击

1、缓存穿透

解决思路 

(1)缓存空对象

当查询数据库,发现也没数据时,给Redis传一个null值,然后把null值返回给前端。

(2)布隆过滤

 //空值解决
@Override
    public Result queryById1(Long id) {
        String key = CACHE_SHOP_KEY + id;
        //1、从redis中查
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2、判断是否存在
        if(StrUtil.isNotBlank(shopJson)){
            //3、存在,直接返回
            Shop shop  = JSONUtil.toBean(shopJson,Shop.class);
            return Result.ok(shop);
        }
        //判断命中的是否是空值
        if(shopJson != null){
            //返回错误信息
            return Result.fail("店铺不存在");
        }
        //4、不存在,查询数据库
        Shop shop = getById(id);
        if(shop == null){
            //5、数据库不存在,返回错误
            //返回空值
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return Result.fail("店铺不存在!");
        }
        //6、数据库存在,写入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //7、返回
        return Result.ok(shop);
    }

2、雪崩

解决思路 

1、设置随机的TTL值--在保存expireTime时,采取随机获取。

2、利用Redis集群提高服务的可用性--哨兵模式

3、给缓存业务添加降级限流策略

4、给业务添加多级缓存

3、缓存击穿

        相对于缓存穿透而言,缓存击穿针对的是某个key是小范围的,而缓存穿透则是查询多个key未命中,是大范围的。 可根据这一点区分两者,避免造成知识混乱。

解决思路

1、互斥锁

2、逻辑过期

//互斥锁
public Shop queryWithMutex1(Long id) {
        String key = CACHE_SHOP_KEY + id;
        // 1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        // 判断命中的是否是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return null;
        }

        // 4.实现缓存重建
        // 4.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        Shop shops = new Shop();
        try {
            boolean isLock = tryLock(lockKey);
            // 4.2.判断是否获取成功
            if (!isLock) {
                // 4.3.获取锁失败,休眠并重试
                Thread.sleep(50);
                return queryWithMutex1(id);
            }
            // 4.4.获取锁成功,根据id查询数据库
            Shop shop = getById(id);
            // 5.不存在,返回错误
            if (shop == null) {
                // 将空值写入redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            shops = shop;
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        return shops;
    }
//逻辑过期
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    public Shop  queryWithLogicalExpire1(Long id) {
        String key = CACHE_SHOP_KEY + id;
        // 1.从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.判断是否存在
        if (StrUtil.isBlank(json)) {
            // 3.存在,直接返回
            return null;
        }
        // 4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())) {
            // 5.1.未过期,直接返回店铺信息
            return shop;
        }
        // 5.2.已过期,需要缓存重建
        // 6.缓存重建
        // 6.1.获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2.判断是否获取锁成功
        if (isLock){
            // 6.3.成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    // 查询数据库
                    // 重建缓存
                    this.saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    // 释放锁
                    unlock(lockKey);
                }
            });
        }
        // 6.4.返回过期的商铺信息
        return shop;
    }

    private Shop saveShop2Redis(Long id,Long expireSeconds){
        //查询店铺
        Shop shop = getById(id);
        //封装过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        //写入Redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(redisData));
        return  shop;
    }
//获取锁,释放锁
 private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
 }

private void unlock(String key) {
        stringRedisTemplate.delete(key);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值