关于Lock/Unlock可以参考 Atomic Operation and Atomic Command
简单的用法:
SETNX lock.foo
但是无法处理deadlock。处理deadlock也不能简单粗暴的DEL lock.foo,否则还是可能会发生race condition。
比如两个client发出的请求最终的执行顺序可能如下,从而导致两个client都认为自己lock成功。
C1: SETNX lock.foo C2: SETNX lock.foo C1: DEL lock.foo C1: SETNX lock.foo C2: DEL lock.foo C2: SETNX lock.foo
问题的关键还是我们需要一个Atomic Operation。
Redis Manual上提到的一个方法,用Tcl语言表示如下:
proc acquire_lock {$key {wait 3000}} {
if [redis SETNX $key] return ok
set timestamp [redis GET $key]
if ![expired $timestamp] {
after $wait
return [ acquire_lock $key $wait ]
}
set timestamp [$redis GETSET $key $new_timestamp]
if ![expired $timestamp] {
after $wait
return [ acquire_lock $key $wait ]
}
return ok
}
对于主动unlock的进程来说,也需要格外小心,避免无意中unlock了其它进程获得的lock。
这种情况的一种可能情形如下:
C1: SETNX lock ;# OK ... some time fly by ... ... the lock has expired ... ... then other client have already got the lock ... C1: DEL lock ;# FAIL: delete other's lock
即使"DEL"命令是在过期前发出的,理论上也可能会由于网络延迟等原因而删除了其它里程已经获得的锁。
对Redis来说,EVAL命令是atomic的,因此可以借助LUA语言进行Lock操作:
# lock
$redis set $key NX EX $token ;# SETNX $key
# unlock
DEL $key
# unlock robust
$redis eval {
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
} $key $token
使用'$token'来避免删除其它进程的lock。
Redis也可以通过MULTI和EXEC命令进行transaction操作,从而实现Atomic。
MULTI SET lock.foo $timestamp ... EXEC
但考虑到获得lock的操作是需要逻辑判断语句的参与,因此单独的transaction似乎对于lock不是很直接有效。