规模的key失效,而缓存击穿是一个热点的Key,有大并发集中对其进行访问,突然间这个Key失效了,导致大并发全部打在数据库上,导致数据库压力剧增。这种现象就叫做缓存击穿。
解决方案
1、上面说过了,如果业务允许的话,对于热点的key可以设置永不过期的key。
2、使用互斥锁。如果缓存失效的情况,只有拿到锁才可以查询数据库,降低了在同一时刻打在数据库上的请求,防止数据库打死。当然这样会导致系统的性能变差。
缓存穿透
什么是缓存穿透?
我们使用Redis大部分情况都是通过Key查询对应的值,假如发送的请求传进来的key是不存在Redis中的,那么就查不到缓存,查不到缓存就会去数据库查询。假如有大量这样的请求,这些请求像“穿透”了缓存一样直接打在数据库上,这种现象就叫做缓存穿透。
分析
关键在于在Redis查不到key值,这和缓存击穿有根本的区别,区别在于缓存穿透的情况是传进来的key在Redis中是不存在的。假如有黑客传进大量的不存在的key,那么大量的请求打在数据库上是很致命的问题,所以在日常开发中要对参数做好校验,一些非法的参数,不可能存在的key就直接返回错误提示,要对调用方保持这种“不信任”的心态。
解决方案
1、把无效的Key存进Redis中。如果Redis查不到数据,数据库也查不到,我们把这个Key值保存进Redis,设置value="null",当下次再通过这个Key查询时就不需要再查询数据库。这种处理方式肯定是有问题的,假如传进来的这个不存在的Key值每次都是随机的,那存进Redis也没有意义。
2、使用布隆过滤器。布隆过滤器的作用是某个 key 不存在,那么就一定不存在,它说某个 key 存在,那么很大可能是存在(存在一定的误判率)。于是我们可以在缓存之前再加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回。
突发性热点缓存重建导致系统压力暴增问题
大V带货,一般是冷门数据,热点商品一般不需要大v带货,由于冷门数据不在缓存,因此会导致大量访问直接请求到数据库。
加锁:
第一个请求进同步代码块会新建key,后面的请求排队过来就不会一直请求数据库,而是直接从缓存中查找了。写成单例设计模式中的DCL的形式。
public Product get(Long productId) {
Product product = null;
string productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;
product = getProductFromcache(productCacheKey);
if (product !=null ){
return product;
}
RLock hotCacheCreateLock = redisson.getLock( LOCK_HOT_CACHE_CREATE_PREFIX + productTd);
hotCacheCreateLock.lock();
try {
product = getProductFromCache(productCacheKey);
if (product != null) {
return product;
}
product = productDao. get(productId);
if (product != null) {
redisutil.set(productCacheKey,JSON.toJSoNstring(product),
genProductCacheTimeout(0),TimeUnit.SECONDS);
}else {
redisUtil.set(productCacheKey,
EMPTY_CACHE,
genEmptyCacheTimeout(),
TimeUnit.SECONDS);
}finally {
hotCacheCreateLock. unlockO);
}
return product;
}
缺点:
- synchronized锁是本地jvm的锁。
- 如果有两个直播间:A和B,直播间A的商品上锁,B的商品也需要等待。即synchronized(对象){}
还要维护一个商品id对象,不同的商品用不同(商品id对象)的锁方式。或者使用分布锁,使用分布锁,所有问题都解决了
public static final String Lock_HOT_CREATE_PREFIX="lock:hot_cache_create";
redisson.getLock(Lock_HOT_CREATE_PREFIX+productId);
hotCacheCreateLock.lock();
try{
// 逻辑代码 从缓存中获取key,如果获取不到就查数据库。
// ......
}final{
hotCacheCreateLock.unlock(); // del(lockKey)
}
分布式锁解决数据库和缓存双写不一致问题
线程3在更新缓存前,线程2执行了写数据库和更新缓存操作,结果缓存和数据库不一致
删除缓存的方法也不行,因为本来线程3就是缓存为空,就是要查数据库给key赋值。
解决方案
分布式锁。问题是由于并发读写同一条数据导致的。
读缓存和查找缓存都使用同一把锁,一个线程拿到锁另一个线程不能修改数据
public Product get(Long productId) {
Product product = null;
string productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;
product = getProductFromcache(productCacheKey);
if (product !=null ){
return product;
}
RLock hotCacheCreateLock = redisson.getLock( LOCK_HOT_CACHE_CREATE_PREFIX + productTd);
hotCacheCreateLock.lock();
try {
product = getProductFromCache(productCacheKey);
if (product != null) {
return product;
}
RLock updateProductLock =
redisson.getLock( LOCK_PRODUCT_UPDATE_PREFIX + productId);
updateProductLock.lock();
try {
product = productDao. get(productId);
if (product != null) {
redisutil.set(productCacheKey,JSON.toJSoNstring(product),
genProductCacheTimeout(0),TimeUnit.SECONDS);
}else {
redisUtil.set(productCacheKey,
EMPTY_CACHE,
genEmptyCacheTimeout(),
TimeUn