您现在的位置是:首页 >技术教程 >Redis 缓存穿透、缓存雪崩、缓存击穿网站首页技术教程

Redis 缓存穿透、缓存雪崩、缓存击穿

零维展开智子 2023-07-07 12:00:03
简介Redis 缓存穿透、缓存雪崩、缓存击穿

缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

常见的解决方案有两种:

        缓存空对象

                 优点:实现简单,维护方便

                缺点: 额外的内存消耗 可能造成短期的不一致(恶意攻击的对象id对应的redis空值缓存失效前成为了新插入的id,造成真实客户只能得到空缓存)

        布隆过滤(类似于位示图0、1代表是否存在)

                优点:内存占用较少,没有多余key

                缺点: 实现复杂 存在误判可能(对于过滤结果:假的一定为假,真的有小概率为假)

 

 缓存穿透的解决方案有哪些?

缓存null值

布隆过滤

增强id的复杂度,避免被猜测id规律

做好数据的基础格式校验

加强用户权限校验

做好热点参数的限流

代码解释,这是一个通用的工具类方法:

public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){
        String key=keyPrefix+id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)){
            return JSONUtil.toBean(json,type);
        }
        if (json!=null){
            return null;
        }
        R apply = dbFallback.apply(id);
        if (apply==null){    //生成空值缓存,避免缓存穿透
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);  //对象转Json
            return null;
        }
        this.set(key,JSONUtil.toJsonStr(apply),time, unit);
        return apply;
    }

缓存雪崩

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

解决方案:

给不同的Key的TTL添加随机值

利用Redis集群提高服务的可用性

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

给业务添加多级缓存

缓存击穿

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

常见的解决方案有两种:

互斥锁、逻辑过期

 

基于代码实现互斥锁

public <R,ID> R queryWithMutex(String keyPrefix,ID id,Class<R> type,Function<ID,R> dbrFallback,Long time,TimeUnit unit){
        String key= keyPrefix+id;
        String shopJson= stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(shopJson)){  //命中Redis缓存
            return JSONUtil.toBean(shopJson, type);
        }
        if(shopJson!=null){     //命中空值缓存,代表恶意攻击
            return null;
        }

        if (!tryLock(LOCK_SHOP_KEY+id)) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return queryWithMutex(keyPrefix,id,type,dbrFallback,time,unit);
        }
        String shopJson2= stringRedisTemplate.opsForValue().get(key);
        //doubleCheck
        if (StrUtil.isNotBlank(shopJson2)){  //命中Redis缓存
            unlock(LOCK_SHOP_KEY+id);
            return JSONUtil.toBean(shopJson2, type);
        }
        if(shopJson2!=null){     //命中空值缓存,代表恶意攻击
            unlock(LOCK_SHOP_KEY+id);
            return null;
        }
        R apply = dbrFallback.apply(id);
        if (apply==null){    //生成空值缓存,避免缓存穿透
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);  //对象转Json
            return null;
        }
        this.setWithLogicalExpire(key,JSONUtil.toJsonStr(apply),time,unit);
        unlock(LOCK_SHOP_KEY+id);
        return apply;
    }

基于代码实现逻辑过期 

    public <R,ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit unit){
        String key= CACHE_SHOP_KEY+id;
        String Json= stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(Json)){  //未命中Redis缓存
            return null;
        }
        RedisData redisData = JSONUtil.toBean(Json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        if (redisData.getExpireTime().isAfter(LocalDateTime.now())){
            return r;
        }
        if (tryLock(key)){
            String json=stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isNotBlank(json)){  //命中Redis缓存 二次命中缓存,兜底
                RedisData redisData1 = JSONUtil.toBean(json, RedisData.class);
                if (redisData1.getExpireTime().isAfter(LocalDateTime.now())){
                    unlock(key);
                    return JSONUtil.toBean((JSONObject) redisData1.getData(), type);
                }
            }
            CACHE_REBUILD_EXECUTOR.submit(()->{
                R apply = dbFallback.apply(id);
                this.setWithLogicalExpire(key,apply,time,unit);
                unlock(key);
            });
        }
        return r;
    }

附工具类方法

    public void set(String key, Object value, Long time, TimeUnit util){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,util);
    }
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit util){
        RedisData redisData=new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(util.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
    }
    //线程池
    public static final ExecutorService CACHE_REBUILD_EXECUTOR =             
    Executors.newFixedThreadPool(10);
    /**
     * 获取互斥锁
     * @param key
     * @return
     */
    public Boolean tryLock(String key){
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    /**
     * 释放互斥锁
     * @param key
     * @return
     */
    public void unlock(String key){
        stringRedisTemplate.delete(key);
    }

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。