Redis应用问题
1.缓存穿透
1.1问题描述
key对应的数据不存在,每次获取该key的请求都获取不到缓存,导致数据库压力变大
1.2解决方案
(1)对空值缓存
- 缓存空结果:如果一个查询结果为空,我们仍进行缓存,并且设置空结果的缓存过期时间会很短,一般不超过5分钟
- 设置可访问的白名单:使用bitmaps类型定义一个白名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,不存在进行拦截
- 采用布隆过滤器
- 实时监控:监控Redis缓存命中率,当缓存命中率极速降低时,需要排查访问对象和访问数据
2.缓存击穿
2.1问题描述
key对应数据已过期,当访问该key的大量并发请求发现数据过期,会去访问数据库获取数据,这些并发请求可能会导致数据库崩溃
2.2解决方案
- 预先设置热门数据:在Redis访问高峰之前,预先缓存热门数据,并延长过期时间。
- 实时调整:实时监控调整热门数据
- 使用锁
3.缓存雪崩
3.1问题描述
在极短时间内,Redis内大量key过期失效,导致大量请求直接访问数据库,造成数据库崩溃
3.2解决方案
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache)
- 使用锁或者队列:不适用于高并发
- 设置过期标志更新缓存:记录缓存数据是否过期,当数据过期触发通知,通知另外的线程去更新key的值
- 将缓存失效的时间分散开:缓存失效的时间基础上添加随机值,使过期时间分散开
Redis分布式锁
1.问题描述
随业务发展需要,由原先的单机部署系统主键演变为分布式部署,导致原单机部署时的并发控制锁策略失效,就衍生出了分布式锁。
2.分布式锁主流实现方案
(1)基于数据库
(2)基于Redis(性能最高)
(3)基于Zookeeper(可靠性最高)
3.基于Redis实现分布式锁
(1)使用setnx命令上锁
(2)使用del命令释放锁
(3)使用expire命令设置自动释放锁的时间
(4)前三组合命令:set key value nx ex 过期时间
4.锁被误释放
4.1问题描述
服务a上锁,设置过期时间,在处理业务逻辑时,锁过期自动释放。服务b上锁,服务a业务逻辑处理完成,释放锁时,将服务b的锁误释放。
4.2解决方案
使用uuid作为锁的值,每次释放锁,进行uuid的比较,比较通过后才会释放锁,并且锁释放操作使用LUA脚本,保证原子性
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1.获取锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3, TimeUnit.MILLISECONDS);
if (lock){
Object value = redisTemplate.opsForValue().get("num");
//判断num是否为空
if (StringUtils.isEmpty(value)){
return;
}
//有值转换为int
int num = Integer.parseInt(value+"");
//把redis的num值+1+
redisTemplate.opsForValue().set("num",++num);
//释放锁
String lockUuid = (String) redisTemplate.opsForValue().get("lock");
if (uuid.equals(lockUuid)){
redisTemplate.delete("lock");
}
}else {
try {
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
5.分布式锁可用四大条件
(1)互斥性:同一时间,只能有一个用户持有锁
(2)不会发生死锁:即持有锁的客户端哪怕是崩溃也会释放锁(设置锁自动过期时间)
(3)加锁和释放锁必须是同一个用户(使用uuid)
(4)加锁和释放锁的操作保证原子性(lua脚本)