文章目录
一、redis分布式锁
1.实现原理
版本一
用Redis的 setnx key value 命令 ,存在则返回0,不存在则添加key,返回1;相当于加锁
--
方法执行完
finally {del key}
问题:如果方法中服务宕机,没有 del key, key永远存在,导致死锁
版本二:解决死锁
先setnx key value 命令创建锁
再用Redis中的expire key 10 设置过期时间,但是这俩加起来就不是原子操作
redis2.6.12 后支持参数
如新增 key 的过期时间 setex key time value 比如10秒
这俩命令要整合成一个原子性操作 set key value ex nx time
ex :设置键的过期时间(单位秒)
nx :相当于setnx ,只有key不存在,才返回1
--
方法执行完
finally {del key}
问题:如果请求1时间过于长,使得key过期了还没执行完,请求2可以进来了,并且谁都可以随便删除key,使得锁和没有一样
请求1(需要15秒): 创建key1,10秒的时候key1过期,
请求2(需要8秒)进来,重新创建key2,执行了5秒后,请求1删除key2,
请求3(需要5秒)进来了,创建新的key3,3秒后,请求2删除key3,
请求4(需要5秒)进来创建key4,2秒后,请求3删除,key4......
版本三:增加uuid解决其它请求释放锁的问题,增加lua脚本解决删除原子性操作问题
为了防止锁被它人释放,需要创建一个唯一ID,标识为自己
给setex和del 加一个clintID(UUID),使得只能是自己删除自己的key,同时由于删除key时,也需要先get再del,这并不是一个原子操作,需要通过lua脚本实现
但是依然有请求1超长,key1自动过期,请求2进来的问题
版本四:解决锁过期时间不确定的问题
fork一个子线程(看门狗),定时(10秒),去检查主线程的key是否还持有key,如果持有就增加过期时间
但是实现比较麻烦
版本五:使用redisson客户端,解决上述问题
使用redisson框架,客户端
可重入锁
乐观锁
公平锁
读写锁
Redlock(红锁,下面会详细讲)
String lockKey ="key_001"
Rlock redissonLock = redisson.getLock(Lockkey);
//加锁
redissonLock.lock();
--
finally {
redissonLock.unlock();
}
就是我们上面的思路
2.怎么保证原子操作
一:加锁
二:lua脚本
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
3.set命令
EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在键已经存在时,才对键进行设置操作。
因为 SET 命令可以通过参数来实现和 SETNX 、 SETEX 和 PSETEX 三个命令的效果,所以将来的 Redis 版本可能会废弃并最终移除 SETNX 、 SETEX 和 PSETEX 这三个命令。
4.Redis分布式锁的缺点
之前解决的问题:
1. 死锁:设置过期时间
2. 过期时间评估不好,锁提前过期:守护线程,自动续期
3. 锁被别人释放:锁写入唯一标识,释放锁先检查标识,再释放
但是这些都是单机模式情况下,如果时集群情况下呢,发生主从复制呢
试想这样的场景!
1. 客户端 1 在主库上执行 SET 命令,加锁成功
2. 此时,主库异常宕机,SET 命令还未同步到从库上(主从复制是异步的)
3. 从库被哨兵提升为新主库,这个锁在新的主库上,丢失了!
二、zookeeper实现分布式锁
1.原理
zk一个功能就是临时顺序节点,当连接断开,节点删除,并且创建多次,节点号递增
1.客户端A创建节点my_lock , 创建节点成功xxx-00001,然后判断当前节点是不是my_lock下的第一个节点,是的,所以加锁成功
2.客户端B创建节点my_lock ,因为是有序的,所以节点是xxx-00002,然后判断当前节点是不是my_lock下的第一个节点,显然不是,对当前节点的上一个节点(xxx-00001)添加一个监听器
3.客户端A使用完后,释放锁,删除节点xxx-00001,ZK会负责通知监听这个节点的监听器,也就是客户端B的监听器说锁释放了。
4.客户端B开始加锁,重复上面的操作
2.优点
- 没有死锁问题
- 没有过期问题,当客户端连接断开,临时节点自动删除
参考文章:
https://zhuanlan.zhihu.com/p/150637733
https://zhuanlan.zhihu.com/p/425230130