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;
}
}