对redis的一些基本概念的理解

本文介绍了如何使用Redis作为缓存,包括配置RedisTemplate和StringRedisTemplate的区别,以及缓存更新策略(内存淘汰、超时剔除和主动更新)。重点讲解了缓存穿透、缓存击穿和缓存雪崩的问题及其解决方案。
摘要由CSDN通过智能技术生成

redis是一个基于内存的nosql数据库,由于是基于内存 所以 查询速度会比数据库从磁盘读取快很多 所以我们通常查数据都会先从缓存中查询 未命中之后再去从数据库中获取 之后再存入缓存。如果使用缓存的话 就会有缓存数据与数据库中数据不一致,缓存穿透,缓存击穿,缓存雪崩等问题。

首先说一说使用redis进行缓存 可以使用RedisTemplate或StringRedisTemplate使用RedisTemplate需要配置序列化器否则存储的东西就是一串很长的二进制 以下是配置

 @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //使用fastJson序列化
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        //value值序列化
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        //key值序列化
        template.setKeySerializer(fastJsonRedisSerializer);
        template.setHashKeySerializer(fastJsonRedisSerializer);
        //设置连接
        template.setConnectionFactory(jedisConnectionFactory);
        return template;
    }

{ "@type": "com.ztq.pojo.User", "id": "123456", "mobile": "123456", "name": "sss",  }

这是使用 RedisTemplate 存储的样子,会有一个 @type 字段,虽然也是转换为字符串,但是通过 redisTemplate.opsForValue().get(key);方法获取的时候会转换为我们设置的对象类型,使用起来比较方便。

{"id": "123456", "mobile": "123456", "name": "sss",  }这是使用StringRedisTemplate存储的样子 少了"@type": "com.ztq.pojo.User"可以节省内存,但StringRedisTemplate的value必须是String类型 所以我们在存储对象前需要自己用json工具进行序列化之后再存储取出对象也要自己手动反序列化。

缓存更新策略

1.内存淘汰 当redis内存不足时 自动淘汰部分数据 下次查询时再更新数据 该方案适用于低一致的需求 

2.超时剔除 给缓存数据设置TTL过期时间 到期后 下次查询数据库 更新缓存 该方案时效性一般

3.主动更新 自己写代码 在修改数据库的同时更新缓存 这种方法比较推荐 一致性好 对于是先更新数据库还是先更新 缓存 比较推荐的是先更新数据库 因为如果先更新缓存 而数据库更新时出现了异常 就会导致两者不一致 而对于是选择删除缓存还是选择更新缓存 我们一般选择删除缓存 只有当下次再来访问的时候才重新写入缓存 这样就可以避免一些无效的写操作。

缓存穿透

缓存穿透是指当一个key不存在 并且数据库中也没有该数据 之后如果有大量的这种key过来 那便会对数据库造成很大的压力 所以解决该问题有两种方法 一种是布隆过滤器 一种是存空值 布隆过滤器我了解并不是很多 就不展开讲了 最常用的办法还是存空值 虽然该方法会浪费一定的内存但可以通过设置过期时间来缓解 该方法实现起来就是 当数据库查询不到这个数据时 就给缓存中存一个该key,value为空之后如果再查 那么就可以直接命中缓存 不会走数据库

 
public <R,ID> R chuanTou(String keyPrefix, ID id, Class<R>type, Function<ID,R>sql,Long time,TimeUnit unit){
       String key=keyPrefix+id;
        String s = stringRedisTemplate.opsForValue().get(key);
        if(StringUtils.isNotBlank(s)){
           return JSONUtil.toBean(s, type);
        }
        if(s!=null){
            return null;
        }
        R apply = sql.apply(id);
        if(apply==null){
            stringRedisTemplate.opsForValue().set(key,"",20,TimeUnit.SECONDS);
            return null;
        }
        stringRedisTemplate.opsForValue().set(key,JSONUtils.toJSONString(apply),time,unit);
        return apply;
    }

缓存雪崩

当大量的key在同一时间失效或者redis服务失效 导致大量请求到达数据库 给数据库造成巨大压力

解决方案1.给不同key的过期时间设置一个随机值 2.利用redis集群提高可用性 3.添加多级缓存 4.给缓存业务添加降级限流策略。

缓存击穿

缓存击穿是指当一个热点key过期 在同一时间有大量并发请求到达数据库给数据库造成极大压力解决缓存击穿可以利用互斥锁和设置逻辑过期时间 当一个热点key过期 第一个线程拿到锁 之后去开启一个线程去进行查询数据库更新redis的操作 而当前线程则返回原来过期的数据 而其他线程拿不到锁 也返回过期数据 这里的锁可以用setIfAbsent方法设置一个key在redis中 如果其他人如果也想获取锁就也要设置该key在redis中 但如果key存在该方法返回false 就相当于无法获取锁 释放锁就可以删除该key 同时也要给该key设置过期时间防止异常无法释放锁 代码如下

 private static final ExecutorService CACHE_EXECUTOR= Executors.newFixedThreadPool(10);
 public <R,ID>R jichuan(String keyPrefix, ID id,Class<R>type,Function<ID,R>sql,TimeUnit unit,Long time){
        String key=keyPrefix+id;
        String s = stringRedisTemplate.opsForValue().get(key);
        if(StringUtils.isBlank(s)){
            return null;
        }
        RedisData redisData = JSONUtil.toBean(s, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        if(redisData.getExpireTime().isAfter(LocalDateTime.now())){
            return r;
        }
        String lockKey="lock"+id;
        if(tryLock(lockKey)){
            CACHE_EXECUTOR.submit(()->{
                try {
                    R apply = sql.apply(id);
                    redisData.setData(apply);
                    redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
                    stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
                }catch (Exception e){
                    throw new RuntimeException(e);
                }finally {
                    unLock(lockKey);
                }
            });
        }
        return r;
    }
  public  boolean tryLock(String key){
      Boolean absent = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
      return BooleanUtil.isTrue(absent);
  }
  public void unLock(String key){
       stringRedisTemplate.delete(key);
  }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值