redis缓存问题

缓存穿透:指大量请求的数据在缓存和数据库都没有,这些请求全打在了数据库上

解决方案:

        1、缓存空对象:把数据库不存在的数据也存入redis里面,并把值设为空对象

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

                缺点:

                           额外的内存消耗

                           可能造成短期的数据不一致

        2、布隆过滤:如果通过布隆过滤判断数据不存在,那就是不存在,如果判断数据存在就不能判断真假

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

                缺点:

                           实现复杂

                           存在误判可能

        3、其他解决方法:

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

                做好数据的基础格式校验

                加强用户权限校验

                做好热点参数的限流

布隆过滤:布隆过滤器其实采用的是哈希思想来解决这个问题,通过一个庞大的二进制数组,走哈希思想去判断当前这个要查询的这个数据是否存在,如果布隆过滤器判断存在,则放行,这个请求会去访问redis,哪怕此时redis中的数据过期了,但是数据库中一定存在这个数据,在数据库中查询出来这个数据后,再将其放入到redis中,假设布隆过滤器判断这个数据不存在,则直接返回。

使用方法1的流程图

//缓存穿透设置空值
    private Shop queryWithPassThrough(Long id){
        String key = CACHE_SHOP_KEY+id;
        //查看redis里面是否有商店数据
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //命中了
        if(StrUtil.isNotBlank(shopJson)){
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        if(shopJson != null){
            //返回一个错误信息
            return null;
        }
        //redis里面不存在,根据id查询数据库
        Shop shop = getById(id);
        if(shop == null){
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return null;
        }
        //存在写入redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
        return shop;
    }

缓存雪崩:同一时间大量在redis里面的key同时失效或者redis服务器宕机,导致大

                  量请求都打在了数据库上,带来巨大压力

解决方法:

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

  • 利用Redis集群提高服务的可用性(防止一个redis宕机)

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

  • 给业务添加多级缓存

缓存击穿:也叫热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key

                  突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

逻辑分析:假设线程1在查询缓存之后,本来应该去查询数据库,然后把这个数据重新加载到缓存的,此时只要线程1走完这个逻辑,其他线程就都能从缓存中加载这些数据了,但是假设在线程1没有走完的时候,后续的线程2,线程3,线程4同时过来访问当前这个方法, 那么这些线程都不能从缓存中查询到数据,那么他们就会同一时刻来访问查询缓存,都没查到,接着同一时间去访问数据库,同时的去执行数据库代码,对数据库访问压力过大。

解决方法:

                互斥锁

                逻辑过期

互斥锁:可以利用redis里面的setnx方法来表示获取锁,该方法含义是redis中如果没有这个key,则插入成功,返回1,在 stringRedisTemplate中返回true, 如果有这个key则插入失败,则返回0,在stringRedisTemplate返回false,我们可以通过true,或者是false,来表示是否有线程成功插入key,成功插入的key的线程我们认为他就是获得到锁的线程。

//使用redis里面的setnx作为锁
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);
}
//利用锁解决缓存击穿问题
    private Shop queryWithMutex(Long id){
        String key = CACHE_SHOP_KEY+ id;
        //在redis里面查询是否存在数据
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        if(StrUtil.isNotBlank(shopJson)){
//            存在直接返回
            return JSONUtil.toBean(shopJson,Shop.class);
        }
        //命中但为无效值
        if(shopJson != null){
            return null;
        }

        String lockKey = LOCK_SHOP_KEY+id;
        try {
            //实现缓存重建
            //redis不存在
            //获取锁

            boolean isLock = tryLock(lockKey);
            if(!isLock){
                //失败
                //等待一段时间,回头重新执行
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //成功
            //获取锁成功应再次判断redis里面是否已经有数据了,doubleCheck
            shopJson = stringRedisTemplate.opsForValue().get(key);
            if(StrUtil.isNotBlank(shopJson)){
//            存在直接返回
                return JSONUtil.toBean(shopJson,Shop.class);
            }
            //命中但为空值
            if(shopJson == null){
                return null;
            }
            //查数据库,存入redis
            Shop shop = getById(id);
            if(shop == null){
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return null;
            }
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,TimeUnit.MINUTES);
            return shop;
        }catch (InterruptedException e){
            throw new RuntimeException(e);
        }finally {
            //存在,直接放回数据
            unLock(lockKey);
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值