阅读本文大概需要 6.4 分钟。
各个版本的Redis分布式锁
V1.0
tryLock(){
SETNX Key 1
EXPIRE Key Seconds
}
release(){
DELETE Key
}
V1.1 基于GETSET
tryLock(){
NewExpireTime=CurrentTimestamp+ExpireSeconds
if(SETNX Key NewExpireTime Seconds){
oldExpireTime = GET(Key)
if( oldExpireTime < CurrentTimestamp){
NewExpireTime=CurrentTimestamp+ExpireSeconds
CurrentExpireTime=GETSET(Key,NewExpireTime)
if(CurrentExpireTime == oldExpireTime){
return 1;
}else{
return ;
}
}
}
}
release(){
DELETE key
}
SETNX(Key,ExpireTime)获取锁
如果获取锁失败,通过GET(Key)返回的时间戳检查锁是否已经过期
GETSET(Key,ExpireTime)修改Value为NewExpireTime
检查GETSET返回的旧值,如果等于GET返回的值,则认为获取锁成功
注意:这个版本去掉了EXPIRE命令,改为通过Value时间戳值来判断过期
在锁竞争较高的情况下,会出现Value不断被覆盖,但是没有一个Client获取到锁
在获取锁的过程中不断的修改原有锁的数据,设想一种场景C1,C2竞争锁,C1获取到了锁,C2锁执行了GETSET操作修改了C1锁的过期时间,如果C1没有正确释放锁,锁的过期时间被延长,其它Client需要等待更久的时间
V2.0 基于SETNX
tryLock(){
SETNX Key 1 Seconds
}
release(){
DELETE Key
}
C1成功获取到了锁,之后C1因为GC进入等待或者未知原因导致任务执行过长,后在锁失效前C1没有主动释放锁
C2在C1的锁超时后获取到锁,并且开始执行,这个时候C1和C2都同时在执行,会因重复执行造成数据不一致等未知情况
C1如果先执行完毕,则会释放C2的锁,此时可能导致另外一个C3进程获取到了锁
由于C1的停顿导致C1 和C2同都获得了锁并且同时在执行,在业务实现间接要求必须保证幂等性
C1释放了不属于C1的锁
V3.0
tryLock(){
SETNX Key UnixTimestamp Seconds
}
release(){
EVAL(
//LuaScript
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return
end
)
}
V3.1
tryLock(){
SET Key UniqId Seconds
}
release(){
EVAL(
//LuaScript
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return
end
)
}
分布式Redis锁:Redlock
Redlock在系统模型上尤其是在分布式时钟一致性问题上提出了假设,实际场景下存在时钟不一致和时钟跳跃问题,而Redlock恰恰是基于timing的分布式锁
另外Redlock由于是基于自动过期机制,依然没有解决长时间的gc pause等问题带来的锁自动失效,从而带来的安全性问题。
总结
安全性:在同一时间不允许多个Client同时持有锁
活性
死锁:锁终应该能够被释放,即使Client端crash或者出现网络分区(通常基于超时机制)
容错性:只要超过半数Redis节点可用,锁都能被正确获取和释放
Efficiency(效率):只需要一个Client来完成操作,不需要重复执行,这是一个对宽松的分布式锁,只需要保证锁的活性即可;
Correctness(正确性):多个Client保证严格的互斥性,不允许出现同时持有锁或者对同时操作同一资源,这种场景下需要在锁的选择和使用上更加严格,同时在业务代码上尽量做到幂等
<END>
推荐阅读:
美国程序员把工作外包给中国程序员,啥也不干年入 20 万美元,这操作也是骚
微信扫描二维码,关注我的公众号
朕已阅