Redis

Redis缓存常见问题

缓存穿透--查询一定不存在数据

缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不
存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

有很多种方法可以有效地解决缓存穿透问题,我们选择一种,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五
分钟。

缓存穿透解决--存入这个null

非注解解决

RedisUtils

@Component
public class RedisUtils {
 @Autowired
 RedisTemplate<Object,Object> redisTemplate;
 @Autowired
 StringRedisTemplate stringRedisTemplate;
 /**
 * 向redis中存入string类型数据
 * @param key
 * @param value
 * @return
 */
 public boolean set(String key,Object value) {
 try {
 redisTemplate.opsForValue().set(key,value);
 } catch (Exception e) {
 e.printStackTrace();
 return false;
 }
        return true;
 }
 /**
 * 向redis中存入string类型数据,并指定key的过期时间
 * @param key
 * @param value
 * @return
 */
 public boolean set(String key,Object value,long timeout,TimeUnit
timeUnit) {
 try {
 redisTemplate.opsForValue().set(key,value,timeout,timeUnit);
 } catch (Exception e) {
 e.printStackTrace();
 return false;
 }

 return true;
 }
 /**
 * 根据key取出redis中的string类型数据
 * @param key
 * @return
 */
 public Object get(String key) {
 return StringUtils.isEmpty(key)? null : 
redisTemplate.opsForValue().get(key);
 }
 /**
 * 向redis中存入hash类型数据
 * @param field
 * @param key
 * @param value
 * @return
 */
 public boolean hset(String key,Object field, Object value) {
 try {
 redisTemplate.opsForHash().put(key,field,value);
 } catch (Exception e) {
 e.printStackTrace();
 return false;
 }
 return true;
 }
 /**
 * 根据key和field取出redis中的hash类型的数据
 * @param key
 * @param field
 * @return
 */
 public Object get(String key, Object field) {
 return StringUtils.isEmpty(key)? null : 
redisTemplate.opsForHash().get(key,field);
 }
 /**
 * 通用方法:根据key删除对应的数据
 * @param key
 */
 public void del(String key) {
 redisTemplate.delete(key);
 }
 /**
 * 通用方法:判断key是否存在
 * @param key
 * @return
 */
 public boolean exist(String key) {
 try {
 return redisTemplate.hasKey(key);
 更多精彩好课 
https://bwonline.ke.qq.com/
修改shop-product-provider的SkuServiceImpl
 } catch (Exception e) {
 e.printStackTrace();
 return false;
 }
 }
 /**
 * 为某个key设置过期时间
 * @param key
 * @param timeout
 * @return
 */
 public boolean expire(String key, long timeout) {
 try {
 return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
 } catch (Exception e) {
 e.printStackTrace();
 return false;
 }
 }
 /**
 * 查看key的剩余存活时间
 * @param key
 * @return
 */
 public long ttl(String key) {
 return redisTemplate.getExpire(key, TimeUnit.SECONDS);
 }
}

@Service
public class SKUServiceImpl implements SKUService {
    @Autowired
    SkuInfoMapper skuInfoMapper;
    @Autowired
    SkuAttrValueMapper skuAttrValueMapper;
    @Autowired
    SkuSaleAttrValueMapper skuSaleAttrValueMapper;
    @Autowired
    SkuImageMapper skuImageMapper;
    @Autowired
    SkuInfoCustomMapper skuInfoCustomMapper;
    @Autowired
    RedisUtils redisUtils;

    //......................省略其它代码..............................
    
    @Override
    public SkuInfo findBySkuInfoId(Integer skuInfoId) {
        String key = "sku:" + skuInfoId + ":info";
        Object value = redisUtils.get(key);
        SkuInfo skuInfo;
        if (value != null) {
            //说明缓存中缓存这个sku,直接取出缓存数据
             skuInfo = new Gson().fromJson(value.toString(), SkuInfo.class);
       } else {
            //说明缓存中没有缓存这个sku,查数据并缓存
            //1.查询sku_info表
            skuInfo =
skuInfoMapper.selectByPrimaryKey(skuInfoId.longValue());
            if (skuInfo != null) {
                //2.查询sku_image表:sku对应的图片
                SkuImageExample skuImageExample = new SkuImageExample();
               
skuImageExample.createCriteria().andSkuIdEqualTo(skuInfoId.longValue());
                List<SkuImage> skuImages =
skuImageMapper.selectByExample(skuImageExample);
                //3.查询sku_sale_attr_value=>sku对应的销售属性值信息
                SkuSaleAttrValueExample skuSaleAttrValueExample = new
SkuSaleAttrValueExample();
               
skuSaleAttrValueExample.createCriteria().andSkuIdEqualTo(skuInfoId.longValu
e());
                List<SkuSaleAttrValue> skuSaleAttrValueList =
skuSaleAttrValueMapper.selectByExample(skuSaleAttrValueExample);
                //4.将skuImages设置到skuInfo中
                skuInfo.setSkuImageList(skuImages);
                skuInfo.setSkuSaleAttrValueList(skuSaleAttrValueList);
                //5.缓存skuInfo到redis
                redisUtils.set(key, new Gson().toJson(skuInfo));
           } else {
       //如果skuInfo为null,将null缓存到redis,过期时间设置为5min!!!!!!!!!!!!!!!!
         redisUtils.set(key, new Gson().toJson(null), 5, 
TimeUnit.MINUTES);
           }
       }
        return skuInfo;
   }
  
   
}

注解解决缓存穿透

需要配置cacheManager

@SuppressWarnings("ALL")
@Configuration
public class RedisConfig {
 @Bean
 public RedisTemplate<Object, Object>
redisTemplate(RedisConnectionFactory redisConnectionFactory)
 throws UnknownHostException {
 RedisTemplate<Object, Object> template = new RedisTemplate<>();
 template.setConnectionFactory(redisConnectionFactory);
 //设置序列化器
 StringRedisSerializer serializer = new StringRedisSerializer();
 template.setKeySerializer(serializer);
 template.setValueSerializer(serializer);
 template.setHashKeySerializer(serializer);
 template.setHashValueSerializer(serializer);
 return template;
 }
 @Bean
 @Primary
 public CacheManager cacheManager(RedisConnectionFactory
redisConnectionFactory){
 更多精彩好课 
https://bwonline.ke.qq.com/
修改shop-product-provider的SkuServiceImpl
 RedisCacheConfiguration cacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
 .disableCachingNullValues() //禁用缓存null,如果设置,那么缓存null
时会抛出异常
 .entryTtl(Duration.ofDays(1))
 
.serializeKeysWith(RedisSerializationContext.SerializationPair
 .fromSerializer(new StringRedisSerializer()))
 
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSeriali
zer(new GenericJackson2JsonRedisSerializer()));
 return
RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfigu
ration).build();
 }
 @Bean
 public CacheManager cacheManagerTTL(RedisConnectionFactory
redisConnectionFactory){
 RedisCacheConfiguration cacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
 //.disableCachingNullValues()
 .entryTtl(Duration.ofMinutes(5))
 
.serializeKeysWith(RedisSerializationContext.SerializationPair
 .fromSerializer(new StringRedisSerializer()))
 
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSeriali
zer(new GenericJackson2JsonRedisSerializer()));
 return
RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfigu
ration).build();
 }
}

提供者代码

  @Override
    @Cacheable(value = "skuInfo",key ="#skuInfoId+':info'",cacheManager =
"cacheManagerTTL")//指定缓存管理器
    public SkuInfo findBySkuInfoId(Integer skuInfoId) {
       // String key = "skuInfo:" + skuInfoId + ":info";
        //1.查询sku_info表
        SkuInfo skuInfo =
skuInfoMapper.selectByPrimaryKey(skuInfoId.longValue());
        if (skuInfo!=null) {
            //2.查询sku_image表:sku对应的图片
            SkuImageExample skuImageExample = new SkuImageExample();
           
skuImageExample.createCriteria().andSkuIdEqualTo(skuInfoId.longValue());
            List<SkuImage> skuImages =
skuImageMapper.selectByExample(skuImageExample);
            //3.查询sku_sale_attr_value=>sku对应的销售属性值信息
            SkuSaleAttrValueExample skuSaleAttrValueExample = new
SkuSaleAttrValueExample();
           
skuSaleAttrValueExample.createCriteria().andSkuIdEqualTo(skuInfoId.longValu
e());
            List<SkuSaleAttrValue> skuSaleAttrValueList =
skuSaleAttrValueMapper.selectByExample(skuSaleAttrValueExample);
            //4.将skuImages设置到skuInfo中
            skuInfo.setSkuImageList(skuImages);
            skuInfo.setSkuSaleAttrValueList(skuSaleAttrValueList);
       }
        return skuInfo;
   }
}

缓存雪崩--解决设置随机数

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发
到DB,DB瞬时压力过重产生雪崩。
一个简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,
比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

非注解解决

    @Override
    public SkuInfo findBySkuInfoId(Integer skuInfoId) {
        String key = "sku:" + skuInfoId + ":info";
        Object value = redisUtils.get(key);
        SkuInfo skuInfo;
        if (value != null) {
            //说明缓存中缓存这个sku,直接取出缓存数据
             skuInfo = new Gson().fromJson(value.toString(), SkuInfo.class);
       } else {
            //说明缓存中没有缓存这个sku,查数据并缓存
            //1.查询sku_info表
            skuInfo = skuInfoMapper.selectByPrimaryKey(skuInfoId.longValue());
            if (skuInfo != null) {
                //2.查询sku_image表:sku对应的图片
                SkuImageExample skuImageExample = new SkuImageExample();
               
skuImageExample.createCriteria().andSkuIdEqualTo(skuInfoId.longValue());
                List<SkuImage> skuImages =
skuImageMapper.selectByExample(skuImageExample);
    
                //3.查询sku_sale_attr_value=>sku对应的销售属性值信息
                SkuSaleAttrValueExample skuSaleAttrValueExample = new
SkuSaleAttrValueExample();
 更多精彩好课 
https://bwonline.ke.qq.com/
4.8.2.3 注解式缓存解决方案
 一般可以采用多级缓存,不同级别的缓存设置不同的超时时间,尽量避免集体失效,由于注解式的灵活度
很低(高度封装),建议使用非注解式解决方案
4.8.3 缓存击穿
4.8.3.1 概述
 在高并发的情况下,大量的请求同时查询同一个key时,此时这个key正好失效了,就会导致同一时
间,这些请求都会去查询数据库,这样的现象我们称为缓存击穿
               
skuSaleAttrValueExample.createCriteria().andSkuIdEqualTo(skuInfoId.longValue())
;
                List<SkuSaleAttrValue> skuSaleAttrValueList =
skuSaleAttrValueMapper.selectByExample(skuSaleAttrValueExample);
    
                //4.将skuImages设置到skuInfo中
                skuInfo.setSkuImageList(skuImages);
                skuInfo.setSkuSaleAttrValueList(skuSaleAttrValueList);
    
           //5.缓存skuInfo到redis
                //redisUtils.set(key, new Gson().toJson(skuInfo));
                //随机时间范围 1min<= timeout<6min
                //redisUtils.set(key, new Gson().toJson(skuInfo), 
ThreadLocalRandom.current().nextInt(5) + 1, TimeUnit.MINUTES);
    
                //随机时间范围:60s<=timeout<301s
                redisUtils.set(key, new Gson().toJson(skuInfo), 
ThreadLocalRandom.current().nextInt(241) + 60, TimeUnit.SECONDS);
                
           } else {
                //如果skuInfo为null,将null缓存到redis,过期时间设置为5min
                redisUtils.set(key, new Gson().toJson(null), 5, 
TimeUnit.MINUTES);
           }
       }
    
        return skuInfo;
   }
   
}

缓存穿透

posted @ 2023-02-02 09:46  拿受用  阅读(15)  评论(0编辑  收藏  举报