聊聊Redis那些事(下篇)
五、Redis分布式锁
概念:对于操作共享的资源时,需要使用Redis锁进行控制(如不是分布式或集群系统 可用JVM级别锁实现)
Redis分布式锁基础实现(有问题的)
// 分布式锁的key
String lock = "lock";
// 只有当Redis中没有这个key 才能设置成功 并设置超时时间
// 注意超时时间需要根据实际业务代码执行时间决定
Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(lock,"lock",30,TimeUnit.SECOND);
if(!getLock){
// 当前线程没有拿到锁 执行没有拿到锁的逻辑
return false
}
// 防止业务代码遇异常而不释放锁
try{
// 拿到了锁 进行业务操作
...
...
}finally{
stringRedisTempLate.delete(lock);
}
// 上面的代码是有问题的
// 思考一下 一旦设置了锁的超时时间 在锁因为到期时间而自动删除时 那么再delete的时候删除的key一定是自己加的吗?
String lock = "lock";
// 在上面代码的基础上 我们对于每个线程都生成一个唯一的值 并且把这个值放到Redis的value中去
String value = UUID.randomUUID().toString(); , //
Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(lock,value,30,TimeUnit.SECOND);
...
...
}finally{
// 保证当前线程加的锁当前线程删
if(value.equals(stringRedisTemplate.opsForValue().get(lock))){
stringRedisTempLate.delete(lock);
}
}
但是上面的代码写成这样还是有问题的 想象一下 如果锁因为过期时间而自动到期 而业务代码未执行完毕 这时候其他线程进来了 还是会产生一定的问题
锁续命Redisson
// 分布式锁的key
String lock = "lock";
// 只有当Redis中没有这个key 才能设置成功 并设置超时时间
// 注意超时时间需要根据实际业务代码执行时间决定
String value = UUID.randomUUID().toString(); , //
Boolean getLock = stringRedisTemplate.opsForValue().setIfAbsent(lock,value,30,TimeUnit.SECOND);
// 拿到一把锁
RLock rlock = Redisson.getLock(lock);
if(!getLock){
// 当前线程没有拿到锁 执行没有拿到锁的逻辑
return false
}
// 锁续命功能
// 默认30s超时
rlock.lock();
// 防止业务代码遇异常而不释放锁
try{
// 拿到了锁 进行业务操作
...
...
}finally{
//解锁
rlock.unlock();
// 保证当前线程加的锁当前线程删
if(value.equals(stringRedisTemplate.opsForValue().get(lock))){
stringRedisTempLate.delete(lock);
}
}
锁续命相当于还是去Redis设置一个锁 并且开辟一个子线程看当前线程是否还持有这把锁 如果业务代码没执行完而因为锁的过期时间而导致锁过期 那么锁会自动续命(延长过期时间)
Redisson利用大量的lua脚本保证多条命令执行的原子性
六、Lua脚本与管道
优点:
操作具有原子性(事务功能)(管道不具备事务功能)
节省网络开销
缺点:
不要再Lua脚本出现耗时的运算 这会导致Redis阻塞(管道不会阻塞)
七、RedLock
用来解决主节点加锁后未同步到从节点时 主节点挂掉 从节点变主节点后 产生的二次加锁的问题
RedLock 把锁发给了半数以上的从节点来保证一致性 但是会带来性能问题
八、Redis相关问题
缓存穿透
缓存穿透指查询了一个数据库和Redis都不存在的key
解决办法:
缓存查询结果(无论是否有数据)
缓存雪崩
大量的key在同一时间失效
解决办法:
设置缓存过期时间时增加基础时间+随机策略
接口限流
过期键清除策略
1.惰性删除: 当读/写一个已经过期的key时候 会删除这个key
2.主动删除: Redis六哦那个淘汰策略主动删除key