Lock/Unlock with Redis

风行水上 @ 2014-02-28 18:52:28
标签:

    关于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"命令是在过期前发出的,理论上也可能会由于网络延迟等原因而删除了其它里程已经获得的锁。

    Atomic with EVAL

    对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。

    Atomic with Transaction

    Redis也可以通过MULTIEXEC命令进行transaction操作,从而实现Atomic。

    MULTI
    SET lock.foo $timestamp
    ...
    EXEC
    

    但考虑到获得lock的操作是需要逻辑判断语句的参与,因此单独的transaction似乎对于lock不是很直接有效。

    标签:

      分享到:
      comments powered by Disqus

      32/36ms