一.缓存穿透问题:
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
常见的解决方案有两种:
·缓存空对象
◆优点:实现简单,维护方便
◆缺点:额外的内存消耗,可能造成短期不一致。
●布隆过滤
◆优点:内存占用较少,没有多余key
◆缺点:实现复杂,
缓存穿透的解决方案有哪些?
缓存null值
布隆过滤
增强id的复杂度,避免被猜测id规律
做好数据的基础格式校验
加强用户权限校验存在误判可能
实例:存入空值
public Result getByIdForBreakDown(Long id){
String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY+id);
if (StrUtil.isNotBlank(cacheShop)) {
//如果不为空直接返回
return Result.ok(JSONUtil.toBean(cacheShop,Shop.class));
}
if (cacheShop != null) {
//缓存为空,返回错误信息
return Result.fail("店铺信息为空");
}
Shop shop = getById(id);
if (shop != null) {
//店铺不为空,返回
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop),30L,TimeUnit.MINUTES);
return Result.ok(shop);
}
//如果为空,写入缓存
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,"",3,TimeUnit.MINUTES);
return Result.fail("查询店铺信息为空");
}
缓存雪崩
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis,服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
给不同的Key的TTL添加随机值
利用Redis集群提高服务的可用性
给缓存业务添加降级限流策略
■
给业务添加多级缓存
缓存击穿
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的ky突然失效了,无数的请求访问会
在瞬间给数据库带来巨大的冲击。
常见的解决方案有两种:
互斥锁
逻辑过期
使用SetEx来实现自定义互斥锁
定义锁
@Component
public class Lock {
@Autowired
private static StringRedisTemplate stringRedisTemplate;
public static boolean tryLock(String key){
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(key, "lock",10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(lock);
}
public static boolean removeLock(String key){
Boolean delete = stringRedisTemplate.delete(key);
return BooleanUtil.isTrue(delete);
}
}
public Shop queryWithPassThrough(long id) {
String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY+id);
if(StrUtil.isNotBlank(cacheShop)){
return JSONUtil.toBean(cacheShop,Shop.class);
}
if (cacheShop != null) {
return null;
}
Shop shop = null;
try {
boolean flag = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
if (!flag) {
Thread.sleep(50);
return queryWithPassThrough(id);
}
shop = getById(id);
if (shop == null) {
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", 3L, TimeUnit.MINUTES);
return null;
}
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 10L, TimeUnit.MINUTES);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
unLock(RedisConstants.LOCK_SHOP_KEY+id);
}
return shop;
}
}
定义一个线程池
private static final ExecutorService CACHE_REBUILD_EXCUTOR= Executors.newFixedThreadPool(10);
定义一个逻辑时间类,把Shop放进去。
@Data
public class RedisData {
private LocalDateTime expireTime;
private Object data;
}
代码实现:
public Result getTypeShop(Integer typeId, Integer current) {
//TODO 分页查询拿到List存入缓存
String cache=RedisConstants.CACHE_SHOP_KEY+"type:";
String cacheList = stringRedisTemplate.opsForValue().get(cache + typeId);
if (StrUtil.isNotBlank(cacheList)){
JSONArray strings = JSONUtil.parseArray(cacheList);
System.out.println(strings);
List<Shop> shops = strings.stream().
map(shop -> JSONUtil.toBean(JSONUtil.toJsonStr(shop), Shop.class))
.collect(Collectors.toList());
return Result.ok(shops);
}
List<Shop> type_id = query().eq("type_id", typeId).page(new Page<Shop>(current, DEFAULT_PAGE_SIZE)).getRecords();
stringRedisTemplate.opsForValue().set(cache+typeId,JSONUtil.toJsonStr(type_id),10L, TimeUnit.MINUTES);
return Result.ok(type_id);
}
// TODO 利用互斥锁解决缓存穿透。
public Shop queryWithPassThrough(long id) {
String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY+id);
if(StrUtil.isNotBlank(cacheShop)){
return JSONUtil.toBean(cacheShop,Shop.class);
}
if (cacheShop != null) {
return null;
}
Shop shop = null;
try {
boolean flag = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
if (!flag) {
Thread.sleep(50);
return queryWithPassThrough(id);
}
shop = getById(id);
if (shop == null) {
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, "", 3L, TimeUnit.MINUTES);
return null;
}
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop), 10L, TimeUnit.MINUTES);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
unLock(RedisConstants.LOCK_SHOP_KEY+id);
}
return shop;
}
public void saveShopToRedis(long id,Long secoend){
Shop shop = getById(id);
RedisData redisData = new RedisData();
redisData.setData(shop);
redisData.setExpireTime(LocalDateTime.now().plusSeconds(secoend));
stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
}
//TODO 逻辑过期解决焕春穿透
public Shop queryByLogic(Long id){
String cacheShop = stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);
if (cacheShop == null) {
return null;
}
RedisData redisData = JSONUtil.toBean(cacheShop, RedisData.class);
JSONObject shopData = (JSONObject) redisData.getData();
Shop shop = JSONUtil.toBean(shopData, Shop.class);
LocalDateTime expireTime = redisData.getExpireTime();
if (expireTime.isAfter(LocalDateTime.now())){
return shop;
}
boolean isLock = tryLock(RedisConstants.LOCK_SHOP_KEY + id);
if (isLock){
//如果成功,开始重建
CACHE_REBUILD_EXCUTOR.submit(()->{
this.saveShopToRedis(id,20L);
//释放锁
unLock(RedisConstants.LOCK_SHOP_KEY+id);
});
}
//失败返回原数据
return shop;
}