文章目录
- 缓存雪崩
- 什么是缓存雪崩
- 解决方案
- 缓存穿透
- 什么是缓存穿透
- 解决方案
- 缓存与数据库读写不一致
- 什么是缓存与数据库读写不一致
- 解决方案
缓存雪崩
什么是缓存雪崩
存在同一时间内大量键(缓存)过期(失效)或redis挂掉,导致之后的大量的请求瞬间都落在了数据库中,导致连接异常!
- 大量键(缓存)过期解释:Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键删除。如果缓存数据设置的过期时间是相同的,并且Redis恰好将这部分数据全部删光了。这就会导致在这段时间内,这些缓存同时失效,全部请求到数据库中。
解决方案
- 设置缓存超时时间的时候加上一个随机时间,比如这个缓存key的超时时间是固定的5分钟加上随机的2分钟,这样可从一定程度上避免雪崩问题
- 使用互斥锁排队:当key获取value值为空时,锁上,从数据库中load数据后再释放锁。若其它线程获取锁失败,则等待一段时间后重试。这里要注意,分布式环境中要使用分布式锁,单机的话用普通的锁(synchronized、Lock)就够了
- 缓存加入随机时间的实现:
public String getByKey(String keyA,String keyB) {
String value = redisService.get(keyA);
if (StringUtil.isEmpty(value)) {
value = redisService.get(keyB);
String newValue = getFromDbById();
redisService.set(keyA,newValue,31, TimeUnit.DAYS);
redisService.set(keyB,newValue);
}
return value;
}
- 互斥锁排队实现
public String getWithLock(String key, Jedis jedis, String lockKey, String uniqueId, long expireTime) {
// 通过key获取value
String value = redisService.get(key);
if (StringUtil.isEmpty(value)) {
// 分布式锁,详细可以参考
//封装的tryDistributedLock包括setnx和expire两个功能,在低版本的redis中不支持
try {
boolean locked = redisService.tryDistributedLock(jedis, lockKey, uniqueId, expireTime);
if (locked) {
value = userService.getById(key);
redisService.set(key, value);
redisService.del(lockKey);
return value;
} else {
// 其它线程进来了没获取到锁便等待50ms后重试
Thread.sleep(50);
getWithLock(key, jedis, lockKey, uniqueId, expireTime);
}
} catch (Exception e) {
log.error("getWithLock exception=" + e);
return value;
} finally {
redisService.releaseDistributedLock(jedis, lockKey, uniqueId);
}
}
return value;
}
缓存穿透
什么是缓存穿透
缓存穿透表示恶意用户模拟请求很多缓存中不存在的数据,由于缓存中都没有,导致这些请求短时间内直接落在了数据库上,导致数据库异常
解决方案
- 使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法的请求就不让这个请求到数据库层
- 使用互斥锁排队(实现同上)
- 接口限流与熔断、降级:重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些服务不可用时候,进行熔断,失败快速返回机制
- 数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了,这种情况我们一般会将空对象设置一个较短的过期时间
缓存与数据库读写不一致
什么是缓存与数据库读写不一致
当我们要更新了数据库的数据时,因为各种情况很可能就造成数据库和缓存的数据不一致
举个简单的例子:假设现在外面数据库的库存是100,但是现在进行了删减操作,数据库只有99了,但是我们的缓存还没有更新,这就造成了数据库数据域缓存不一致。
解决方案
- 操作数据的同时也操作缓存,这里有两种策略:
- 先操作数据库,再操作缓存
- 先操作缓存,再操作数据库
- 更新缓存或者删除缓存