redis缓存使用

添加缓存

查询数据库之前先查询缓存,缓存命中就返回数据,未命中,再查询数据库,并将查询到的数据存入缓存

缓存更新流程

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

缓存穿透流程

 private Result selectThrough(Long id) {
        String shopKey = RedisConstants.CACHE_SHOP_KEY + id;
        // 1.先查询缓存
        Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(shopKey);
        // 2.缓存是否命中
        if(MapUtil.isNotEmpty(shopMap) && shopMap.containsKey("id")){
            // 3.命中,并且数据不为"",直接返回
            Shop shop = BeanUtil.fillBeanWithMap(shopMap, new Shop(), false);
            return Result.ok(shop);
        }
        // 3.1命中,但是数据为""
        if(MapUtil.isNotEmpty(shopMap) && !shopMap.containsKey(id)){
            // 返回错误信息,
            return Result.fail("店铺信息不存在");
        }
        // 4.缓存未命中,再查询数据库
        Shop shop = getById(id);
        // 5.数据库是否命中
        if (shop == null) {
            // 6.未命中,存入缓存""值,设置ttl,返回错误信息
            HashMap<Object, Object> map = MapUtil.newHashMap();
            map.put("","");
            stringRedisTemplate.opsForHash()
                    .putAll(shopKey, map);
            stringRedisTemplate.expire(shopKey,RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("店铺信息不存在");
        }
        // 6.命中,存入缓存,设置ttl,并返回数据
        // 将shop转换为map
        Map<String, Object> map = BeanUtil.beanToMap(shop, new HashMap<>(),
                new CopyOptions()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((field, value) ->{
                          /*  setFieldValueEditor优先级要高于ignoreNullValue导致前者首先被触发,
                          因此出现空指针问题。
                          所以需要在setFieldValueEditor中手动判空。
    */                        if(value != null) {
                                return value.toString();
                            }
                            return null;
                        })
        );
        stringRedisTemplate.opsForHash()
                .putAll(shopKey, map);
        stringRedisTemplate.expire(shopKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        return Result.ok(shop);
    }

缓存雪崩

private Result selectWithMutex(Long id) {
        String shopKey = RedisConstants.CACHE_SHOP_KEY + id;
        // 1.先查询缓存
        Map<Object, Object> shopMap = stringRedisTemplate.opsForHash().entries(shopKey);
        // 2.缓存是否命中
        if(MapUtil.isNotEmpty(shopMap) && shopMap.containsKey("id")){
            // 3.命中,并且数据不为"",直接返回
            Shop shop = BeanUtil.fillBeanWithMap(shopMap, new Shop(), false);
            return Result.ok(shop);
        }
        // 3.1命中,但是数据为""
        if(MapUtil.isNotEmpty(shopMap) && !shopMap.containsKey(id)){
            // 返回错误信息,
            return Result.fail("店铺信息不存在");
        }
        Shop shop = null;
        String lockKey = LOCK_SHOP_KEY + id;
        try {
            // 4.缓存未命中,尝试获取锁
            boolean isLock = tryLock(lockKey);
            if (!isLock){
                // 4.1.没有获取到锁,就休眠一段时间
                Thread.sleep(100);
                // 4.2再次读取缓存
                return selectWithMutex(id);
            }
            // 5.获取到锁,就从数据库查询数据
            shop = getById(id);
            // 5.1数据库是否命中
            if (shop == null) {
                // 6.未命中,存入缓存""值,设置ttl
                 HashMap<Object, Object> map = MapUtil.newHashMap();
                 map.put("","");
                stringRedisTemplate.opsForHash()
                        .putAll(shopKey, map);
                stringRedisTemplate.expire(shopKey,RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                return Result.fail("店铺信息不存在");
            }
            // 6.命中,存入缓存,设置ttl
            // 将shop转换为map
            Map<String, Object> map = BeanUtil.beanToMap(shop, new HashMap<>(),
                    new CopyOptions()
                            .setIgnoreNullValue(true)
                            .setFieldValueEditor((field, value) ->{
                              /*  setFieldValueEditor优先级要高于ignoreNullValue导致前者首先被触发,
                              因此出现空指针问题。
                              所以需要在setFieldValueEditor中手动判空。
        */                        if(value != null) {
                                    return value.toString();
                                }
                                return null;
                            })
            );
            stringRedisTemplate.opsForHash()
                    .putAll(shopKey, map);
            stringRedisTemplate.expire(shopKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
           throw new RuntimeException(e);
        } finally {
            // 7.释放锁
            unLock(lockKey);
        }

        return Result.ok(shop);
    }

    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForHash().putIfAbsent(key, "1", "1");
        stringRedisTemplate.expire(key,RedisConstants.LOCK_SHOP_TTL,TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);

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

缓存击穿-互斥锁

缓存击穿-逻辑过期

​​​​​​​

@Component
@Data
public class CacheService {
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 任意对象序列化为json
     */
    public <R,I> void setStr(String keyPrefix, I id, R r, Long expire, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(keyPrefix + id,JSONUtil.toJsonStr(r),expire,unit);
    }
    // json反序列化
    public <R,I> R getStr(String keyPrefix, I id, Class<R> type){
        String json = stringRedisTemplate.opsForValue().get(keyPrefix + id);
        return JSONUtil.toBean(json, type);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    // 缓存穿透
    public  <R,I> R selectWithThrough(String keyPrefix, I id, Class<R> type,
                                      Function<I,R> dbFallBack,
                                      Long expire,
                                      TimeUnit unit
                                      ) {
        String key = keyPrefix + id;
        // 1.先查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.缓存是否命中
        if(StrUtil.isNotBlank(json)){
            // 3.命中,并且数据不为"",直接返回
            return BeanUtil.toBean(json,type);
        }
        // 3.1命中,但是数据为""
        if(json != null){
            // 返回错误信息,
            return null;
        }
        // 4.缓存未命中,再查询数据库
        R r = dbFallBack.apply(id);
        // 5.数据库是否命中
        if (r == null) {
            // 6.未命中,存入缓存""值,设置ttl,返回错误信息
            stringRedisTemplate.opsForValue().set(key,"",expire,unit);
            return null;
        }
        // 6.命中,存入缓存,设置ttl,并返回数据
        // 将shop转换为map
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r));
        return r;
    }
    private static final String LOCK_SHOP_KEY = "lock:shop:";
    // 缓存击穿-互斥锁
    public <R,I> R selectWithMutex(
            String keyPrefix, I id, Class<R> type,
                                  Function<I,R> dbFallBack,
                                  Long expire,
                                  TimeUnit Unit) {
        String key =  keyPrefix + id;
        // 1.先查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.缓存是否命中
        if(StrUtil.isNotBlank(json)){
            // 3.命中,并且数据不为"",直接返回
            R r = JSONUtil.toBean(json, type);
            return r;
        }
        // 3.1命中,但是数据为""
        if(json != null){
            // 返回错误信息,
            return null;
        }
        R r = null;
        String lockKey = LOCK_SHOP_KEY + id;
        try {
            // 4.缓存未命中,尝试获取锁
            boolean isLock = tryLock(lockKey);
            if (!isLock){
                // 4.1.没有获取到锁,就休眠一段时间
                Thread.sleep(100);
                // 4.2再次读取缓存
                return selectWithMutex(keyPrefix,id,type,dbFallBack,expire,Unit);
            }
            // 5.获取到锁,就从数据库查询数据
            r = dbFallBack.apply(id);
            Thread.sleep(100);
            // 5.1数据库是否命中
            if (r == null) {
                // 6.未命中,存入缓存""值,设置ttl
                stringRedisTemplate.opsForValue().set(key,"",expire,Unit);
                return null;
            }
            // 6.命中,存入缓存,设置ttl
            // 将shop转换为map
           stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(r),expire,Unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 7.释放锁
            unLock(lockKey);
        }

        return r;
    }

    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForHash().putIfAbsent(key, "1", "1");
        stringRedisTemplate.expire(key,RedisConstants.LOCK_SHOP_TTL,TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);

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

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(1);
    // 缓存击穿-逻辑过期
    public <R,I> R selectWithLogicExpire(
            String keyPrefix, I id, Class<R> type,
            Function<I,R> dbFallBack,
            Long expire,
            TimeUnit Unit) {
        String key =  keyPrefix + id;
        // 1.先查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2.缓存是否命中
        if(StrUtil.isBlank(json)){
            // 2.1未命中,直接返回null
            return null;
        }
       // 2.2命中
        // 3.判断key是否过期
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        if(expireTime.isAfter(LocalDateTime.now())){
            // 3.1没有过期直接返回数据
            return r;
        }
        // 3.2过期就尝试获取锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 4.判断是否获取到锁
        if (isLock){
            // 5.获取到锁,就开启一个新的线程来从数据库查询数据
            CACHE_REBUILD_EXECUTOR.submit(() ->{
                try {
                    // 6.查询数据库
                    R newR = dbFallBack.apply(id);
                    // 7.重建缓存
                    setWithLogicalExpire(key,newR,20L,TimeUnit.SECONDS);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    // 8.释放锁
                    unLock(lockKey);
                }
            });
        }
          // 4.2没有获取到锁,返回过期数据
        return r;
    }
}
public Result select(Long id) {
        //return selectThrough(id);
        //return selectWithMutex(id);
        // 解决缓存穿透
        //Shop shop = cacheService
                //.selectWithThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);

        // 互斥锁解决缓存击穿
         //Shop shop = cacheService
                 //.selectWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);

        // 逻辑过期解决缓存击穿
         Shop shop = cacheService
                 .selectWithLogicExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);

        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        // 7.返回
        return Result.ok(shop);
    }

总结

在将数据存入缓存的时候,我选择的hash,那么就需要将符合的对象转换为对应的数据才能存入缓存中

几个易错点

// 6.命中,存入缓存,设置ttl,并返回数据
        // 将shop转换为map
        Map<String, Object> map = BeanUtil.beanToMap(shop, new HashMap<>(),
                new CopyOptions()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((field, value) ->{
                          /*  setFieldValueEditor优先级要高于ignoreNullValue导致前者首先被触发,
                          因此出现空指针问题。
                          所以需要在setFieldValueEditor中手动判空。
    */                        if(value != null) {
                                return value.toString();
                            }
                            return null;
                        })
        );

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值