1、缓存处理流程
接收到查询数据请求时,优先从缓存中查询,若缓存中有数据,则直接返回,若缓存中查不到则从DB中查询,将查询的结果更新到缓存中,并返回查询结果,若DB中查不到,则返回空数据
缓存处理流程.png
2、缓存穿透
当缓存与数据库中都不存在该数据时,由于当数据库查询不到数据就不会写入缓存,这个时候如果用户不断的恶意发起请求,就会导致这个不存在的数据每次请求都会查询DB,请求量大的情况下,就会导致DB压力过大,直接挂掉。
解决方案:
1、当查询返回一个空数据时,直接将这个空数据存到缓存中,过期时间不宜设置过长,建议不超过5分钟
2、采用布隆过滤器:将所有可能存在数据,分别通过多个哈希函数生成多个哈希值,然后将这些哈希值存到一个足够大的bitmap中,此时一个一定不存在的数据就会被这个bitmap拦截,从而减少了数据库的查询压力。
参考链接:https://www.jianshu.com/p/2104d11ee0a2
3、缓存击穿
某一个数据缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,严重情况下会直接挂掉。
解决方案:
1、添加互斥锁:
- ReentrantLock公平锁
- 根据key值加锁,这样线程之间会不影响,不会因为某一个线程获取了锁,其它线程就处于等待时间,也就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据
2、设置热点数据永不过期(物理上的不过期、“逻辑上”的不过期(缓存到期动态构建缓存))
简单的互斥锁例子:
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Jedis jedis;
private final String MUTEX_KEY = "MUTEX_";
public String getData(String key) throws InterruptedException {
String value = stringRedisTemplate.opsForValue().get(key);
//缓存失效
if (StringUtils.isBlank(value)) {
//设置分布式锁,只允许一个线程去查询DB,同时指定过期时间为1min,防止del操作失败,导致死锁,缓存过期无法加载DB数据
if (tryLock(MUTEX_KEY + key, 60L)) {
//从数据库查询数据,将查询的结果缓存起来
value = getValueFromDB();
stringRedisTemplate.opsForValue().set(key, value);
//释放分布式锁
stringRedisTemplate.delete(MUTEX_KEY + key);
} else {
//当锁被占用时,睡眠5s继续调用获取数据请求
Thread.sleep(5000);
getData(key);}
}
return value;
}
/**
* redis实现分布式事务锁 尝试获取锁
*
* @param lockName 锁
* @param expireTime 过期时间
* @return
*/
public Boolean tryLock(String lockName, long expireTime) {
//RedisCallback redis事务管理,将redis操作命令放到事务中处理,保证执行的原子性
String result = stringRedisTemplate.opsForValue().getOperations().execute(new RedisCallback<String>() {
/**
* @param key 使用key来当锁,因为key是唯一的。
* @param value 请求标识,可通过UUID.randomUUID().toString()生成,解锁时通value参数可识别出是哪个请求添加的锁
* @param nx 表示SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作
* @param ex 表示过期时间的单位是秒
* @param time 表示过期时间
*/
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
return jedis.set(lockName, UUID.randomUUID().toString(), "NX", "EX", expireTime);
}
});
if ("OK".equals(result)) {
return true;
}
return false;
}
public String getValueFromDB() {
return "";
}
3、缓存雪崩
缓存中大批量的数据都到了过期时间,从而导致查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同,缓存击穿是指某一条数据到了过期时间,大量的并发请求都来查询这一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
解决方案:
1、设置热点数据永不过期
2、缓存数据的过期时间设置随机,可以在原有的过期时间上加上一个随机值,比如1-3min,防止同一时间大量缓存数据集体失效,导致数据库压力过大。
3、如果是分布式部署缓存数据库,可将热点数据分别存放到不同的缓存数据库中,避免某一点由于压力过大而down掉。
网友评论