How to create a distributed lock with Redis?

Viewed 27838

On the redis documentation, I found a primitive lock can be implemented via SETNX:

http://redis.io/commands/setnx

  • C4 sends SETNX lock.foo in order to acquire the lock

  • The crashed client C3 still holds it, so Redis will reply with 0 to C4.

  • C4 sends GET lock.foo to check if the lock expired. If it is not, it will sleep for some time and retry from the start.

  • Instead, if the lock is expired because the Unix time at lock.foo is older than the current Unix time, C4 tries to perform:

    GETSET lock.foo

  • Because of the GETSET semantic, C4 can check if the old value stored at key is still an expired timestamp. If it is, the lock was acquired.

  • If another client, for instance C5, was faster than C4 and acquired the lock with the GETSET operation, the C4 GETSET operation will return a non expired timestamp. C4 will simply restart from the first step. Note that even if C4 set the key a bit a few seconds in the future this is not a problem.

However, as some users commented, using a UNIX timestamp as the expiration requires the client 's and server's time to be perfectly synchronized. Is there a better alternative to create a global/distributed lock in Redis?

4 Answers

I gem'ed out the SET EX NX solution that misterion mentioned to a cool gem - simple_redis_lock

The code is simple and looks like this:

def lock(key, timeout)
  if @redis.set(key, Time.now, nx: true, px: timeout)
    begin
      yield
    ensure
      release key
    end
  end
end
Related