Class: ThreeScale::Backend::DistributedLock

Inherits:
Object
  • Object
show all
Defined in:
lib/3scale/backend/distributed_lock.rb

Overview

This class uses Redis to implement a distributed lock.

To implement the distributed lock, we use the Redis operation ‘set nx’. The locking algorithm is detailed here: redis.io/topics/distlock Basically, every time that we want to use the lock, we generate a random number and set a key in Redis with that random number if its current value is null. If we could set the value, it means that we could get the lock. To release it, we just need to set to delete the same key.

The implementation used in this class has some limitations. This is limited to a single Redis instance (through Twemproxy), and if the master goes off the mutual exclusion basically does not exist anymore. But there is another thing that breaks the mutual exclusion: whatever we do within the lock critical section is racing against the TTL, and we cannot guarantee that the section will be finished within the limit of the TTL. It can be the case that even if we actually locked Redis, the TTL would have expired before we got the response (think about really bad network conditions or scheduling issues in the computer that is running the critical section). So the lock acts more as an “advisory” lock than a real lock: whatever we execute inside the critical section is “probably going to be with mutual exclusion, but no guarantees”.

Possible ways to minimize the window of this race condition:

1) Do all the work and just lock for committing.
2) Use large values as TTLs as much as possible.

Instance Method Summary collapse

Constructor Details

#initialize(resource, ttl, storage) ⇒ DistributedLock

Returns a new instance of DistributedLock.



33
34
35
36
37
38
# File 'lib/3scale/backend/distributed_lock.rb', line 33

def initialize(resource, ttl, storage)
  @resource = resource
  @ttl = ttl
  @storage = storage
  @random = Random.new
end

Instance Method Details

#current_lock_keyObject



50
51
52
# File 'lib/3scale/backend/distributed_lock.rb', line 50

def current_lock_key
  storage.get(lock_storage_key)
end

#lockObject

Returns key to unlock if the lock is acquired. Nil otherwise.



41
42
43
44
# File 'lib/3scale/backend/distributed_lock.rb', line 41

def lock
  key = lock_key
  storage.set(lock_storage_key, key, nx: true, ex: ttl) ? key : nil
end

#unlockObject



46
47
48
# File 'lib/3scale/backend/distributed_lock.rb', line 46

def unlock
  storage.del(lock_storage_key)
end