从零学习redis06

一.缓存穿透问题:

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
常见的解决方案有两种:
·缓存空对象

◆优点:实现简单,维护方便
◆缺点:额外的内存消耗,可能造成短期不一致。

●布隆过滤
◆优点:内存占用较少,没有多余key
◆缺点:实现复杂,

缓存穿透的解决方案有哪些?
缓存null值
布隆过滤
增强id的复杂度,避免被猜测id规律
做好数据的基础格式校验
加强用户权限校验存在误判可能

实例:存入空值

public Result getByIdForBreakDown(Long id){
        String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY+id);
        if (StrUtil.isNotBlank(cacheShop)) {
            //如果不为空直接返回
           return Result.ok(JSONUtil.toBean(cacheShop,Shop.class));
        }

        if (cacheShop != null) {
            //缓存为空,返回错误信息

            return Result.fail("店铺信息为空");

        }
        Shop shop = getById(id);
        if (shop != null) {
            //店铺不为空,返回
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),30L,TimeUnit.MINUTES);
            return Result.ok(shop);
        }
        //如果为空,写入缓存
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,"",3,TimeUnit.MINUTES);
        return Result.fail("查询店铺信息为空");
    }

缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis,服务宕机,导致大量请求到达数据库,带来巨大压力。

解决方案:
给不同的Key的TTL添加随机值
利用Redis集群提高服务的可用性
给缓存业务添加降级限流策略

给业务添加多级缓存

缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的ky突然失效了,无数的请求访问会
在瞬间给数据库带来巨大的冲击。

常见的解决方案有两种:
互斥锁
逻辑过期

 

使用SetEx来实现自定义互斥锁

定义锁

@Component
public class Lock {
    @Autowired
    private static StringRedisTemplate stringRedisTemplate;
    public static boolean tryLock(String key){
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(key, "lock",10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(lock);

    }
    public static boolean removeLock(String key){
        Boolean delete = stringRedisTemplate.delete(key);
        return BooleanUtil.isTrue(delete);
    }
}

    public Shop queryWithPassThrough(long id)  {
        String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY+id);
        if(StrUtil.isNotBlank(cacheShop)){
            return JSONUtil.toBean(cacheShop,Shop.class);
        }
        if (cacheShop != null) {
            return null;
        }
        Shop shop = null;
        try {

            boolean flag = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
            if (!flag) {
                Thread.sleep(50);
             
                return queryWithPassThrough(id);
            }
           
            shop = getById(id);
            if (shop == null) {
                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", 3L, TimeUnit.MINUTES);
                return null;
            }
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 10L, TimeUnit.MINUTES);


        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {

            unLock(RedisConstants.LOCK_SHOP_KEY+id);
        }

        return shop;


    }


    }

定义一个线程池

   private static final ExecutorService CACHE_REBUILD_EXCUTOR= Executors.newFixedThreadPool(10);

 定义一个逻辑时间类,把Shop放进去。

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

代码实现:

 public Result getTypeShop(Integer typeId, Integer current) {
        //TODO 分页查询拿到List存入缓存
        String cache=RedisConstants.CACHE_SHOP_KEY+"type:";
        String cacheList = stringRedisTemplate.opsForValue().get(cache + typeId);
        if (StrUtil.isNotBlank(cacheList)){
            JSONArray strings = JSONUtil.parseArray(cacheList);
            System.out.println(strings);
            List<Shop> shops = strings.stream().
                    map(shop -> JSONUtil.toBean(JSONUtil.toJsonStr(shop), Shop.class))
                    .collect(Collectors.toList());
            return Result.ok(shops);
        }
        List<Shop> type_id = query().eq("type_id", typeId).page(new Page<Shop>(current, DEFAULT_PAGE_SIZE)).getRecords();
        stringRedisTemplate.opsForValue().set(cache+typeId,JSONUtil.toJsonStr(type_id),10L, TimeUnit.MINUTES);
        return Result.ok(type_id);
    }
    // TODO 利用互斥锁解决缓存穿透。
    public Shop queryWithPassThrough(long id)  {
        String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY+id);
        if(StrUtil.isNotBlank(cacheShop)){
            return JSONUtil.toBean(cacheShop,Shop.class);
        }
        if (cacheShop != null) {
            return null;
        }
        Shop shop = null;
        try {

            boolean flag = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
            if (!flag) {
                Thread.sleep(50);

                return queryWithPassThrough(id);
            }

            shop = getById(id);
            if (shop == null) {
                stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", 3L, TimeUnit.MINUTES);
                return null;
            }
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 10L, TimeUnit.MINUTES);


        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {

            unLock(RedisConstants.LOCK_SHOP_KEY+id);
        }

        return shop;


    }

    public void saveShopToRedis(long id,Long secoend){
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(secoend));
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }
    //TODO 逻辑过期解决焕春穿透
    public  Shop queryByLogic(Long id){
        String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
        if (cacheShop == null) {
            return null;

        }
        RedisData redisData = JSONUtil.toBean(cacheShop, RedisData.class);
        JSONObject shopData = (JSONObject) redisData.getData();
        Shop shop = JSONUtil.toBean(shopData, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (expireTime.isAfter(LocalDateTime.now())){
            return shop;
        }
        boolean isLock = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
        if (isLock){
            //如果成功,开始重建
         CACHE_REBUILD_EXCUTOR.submit(()->{
              this.saveShopToRedis(id,20L);
              //释放锁
             unLock(RedisConstants.LOCK_SHOP_KEY+id);
         });
        }
        //失败返回原数据
        return shop;


    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值