关于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不是很直接有效。