缓存穿透
用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持
久层数据库查询,发现也没有,于是本次查询失败,当用户很多的时候,缓存都没有命
中(秒杀),于是都请求了持久层的数据,这会给持久层造成很大的压力,这时候就相当
于缓存穿透!
解决方案:
布隆过滤器,是一种数据结构,对所有可能查询的参数以hash存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
但是这种方案会存在两个问题:
1.如果空值能够被缓存起来,这就意味着,需要更多的空间存储更多的键,因为这当中
可能会有很多空值的键
2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的
不一致,这对于需要保持一致性的业务会有影响
缓存击穿
指一个key非常热点,在不停的扛着大并发,大并发集中对这一点进行访问,当这个key
在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了
一个洞!
解决方案:
设置热点数据永不过期
从缓存层面来看,没有设置时间,所以不会出现热点key过期后产生的问题
加互斥锁
分布式锁:保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
分布式锁案例:
setnx命令(setIfAbsent #java中写法)此命令适用于实现redis分布式锁
如果为空就set值,并返回1
如果存在(不为空)不进行操作,并返回0
/**
*
* @desc 加锁
* @param key
* @param value
* @param timeout 超时时间
* @param autoReleaseTime 自动释放锁时间
*/
public boolean lock(String key, String value, long timeout, long autoReleaseTime) {
boolean flag = true;
long time = System.currentTimeMillis();
long maxTime = time + timeout;
// 自旋等待-如果在指定时间内还没获取到锁就退出自旋,并且设置过期时间避免死锁。
while (!stringRedisTemplate.opsForValue().setIfAbsent(key, value) && time <= maxTime) {
try {
TimeUnit.MICROSECONDS.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
flag = false;
break;
}
time = System.currentTimeMillis();
}
// 设置过期时间
if (flag) {
stringRedisTemplate.expire(key, autoReleaseTime, TimeUnit.MILLISECONDS);
}
return flag;
}
/**
*
* @desc 解锁
* @param key
* @param value
*/
public void unLock(String key, String value) {
try {
if(StringUtils.isNotBlank(stringRedisTemplate.opsForValue().get(key))
&& stringRedisTemplate.opsForValue().get(key).equals(value)) {
stringRedisTemplate.delete(key);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 扣减库存
*/
public String decreaseStock(String key, String value){
try{
lock(key,value,6000,6000 * 2);
}catch (Exception e){
logger.error(e.getMessage(),e);
}finally {
unLock(key,value);
}
return "";
}
/**
* 测试可模拟多个线程扣减库存
* @param skuId 商品ID
*/
public String test(String skuId) {
decreaseStock("KEY_SKU_"+skuId, skuId);
//线程1
new Thread(()->{
decreaseStock("KEY_SKU_"+skuId, skuId);
});
//线程2
new Thread(()->{
decreaseStock("KEY_SKU_"+skuId, skuId);
});
return "";
}
缓存雪崩
解决方案: