解决Redis中缓存穿透、击穿与雪崩问题:Java

Redis缓存穿透,通常是有人大量访问未知的URL,导致后端穿透Redis,直接访问数据库,这里采用在Redis中注入空key来解决;

Redis缓存击穿,通常为Redis热点key失效问题,导致大量请求同时访问这个key,转而访问数据库,这里采用Redis读延期以及DCl做数据库读取解决;

Redis缓存雪崩,通常为Redis大量key同时失效导致,解决方案,生成随机过期时间,防止同时失效。

//本地缓存map
    private static Map<String,GoodsVo> goodsVoMap=new HashMap<>();
//这个map如果商品过多,有可能超出内存OOM,其次还有分布式缓存一致性问题,这里是本地Map
//也许能通过ConcurrentHashMap实现LRU来解决
    @RequestMapping(value = "/detail/{goodsId}")
    @ResponseBody
    public RespBean toDetail(HttpServletRequest request, HttpServletResponse response, Model model, User user, @PathVariable Long goodsId) {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        GoodsVo goods = getGoodsFromCache(goodsId);
        if (goods == null) {
            //DCL
            boolean lock = valueOperations.setIfAbsent("lock:goodsId:" + goodsId, goodsId, 1, TimeUnit.SECONDS);
            try {
                if (lock) {
                    goods = getGoodsFromCache(goodsId);
                    if (goods == null) {
                        goods = goodsService.findGoodsVoByGoodsId(goodsId);
                        //数据库包含数据
                        if (goods != null) {
                            valueOperations.set("goodsDetail:" + goodsId, JsonUtil.object2JsonStr(goods), genGoodsCacheTimeout(), TimeUnit.SECONDS);
                            goodsVoMap.put("goodsDetail:" + goodsId,goods);
                        } else {
                            //数据库为空,放入空值
                            valueOperations.set("goodsDetail:" + goodsId, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                        }
                    }
                }
            } finally {
                redisTemplate.delete("lock:goodsId:" + goodsId);
            }
        }
        Date startDate = goods.getStartDate();
        Date endDate = goods.getEndDate();
        Date nowDate = new Date();
        //秒杀状态
        int secKillStatus = 0;
        //剩余开始时间
        int remainSeconds = 0;
        if (nowDate.before(startDate)) {
            remainSeconds = (int) ((startDate.getTime() - nowDate.getTime()) / 1000);
        } else if (nowDate.after(endDate)) {
            secKillStatus = 2;
            remainSeconds = -1;
        // 秒杀中
        } else {
            secKillStatus = 1;
            remainSeconds = 0;
        }
        DetailVo detailVo = new DetailVo();
        detailVo.setGoodsVo(goods);
        detailVo.setUser(user);
        detailVo.setRemainSeconds(remainSeconds);
        detailVo.setSecKillStatus(secKillStatus);
        return RespBean.success(detailVo);
    }

    private GoodsVo getGoodsFromCache(Long goodsId) {
        GoodsVo goods = null;
        //jvm进程级别缓存,实际就是内存
        //单机内存比redis更高,可以达到百万级并发
        goods=goodsVoMap.get("goodsDetail:" + goodsId);
        if(goods!=null){
            return goods;
        }
        ValueOperations valueOperations = redisTemplate.opsForValue();
        String goodsDetail = (String) valueOperations.get("goodsDetail:" + goodsId);
        if (!StringUtils.isEmpty(goodsDetail)) {
            if (EMPTY_CACHE.equals(goodsDetail)) {
                //空值返回错误
                redisTemplate.expire("goodsDetail:" + goodsId, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                return new GoodsVo();
            }
            goods = JsonUtil.jsonStr2Object(goodsDetail, GoodsVo.class);
            //读延期
            redisTemplate.expire("goodsDetail:" + goodsId, genGoodsCacheTimeout(), TimeUnit.SECONDS);
        }
        return goods;
    }

    private Integer genGoodsCacheTimeout() {
        return 60 * 60 * 24 + new Random().nextInt(5) * 60 * 60;
    }

    private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值