Distributed locks are a very useful primitive in many environments where different processes require to operate with shared resources in a mutually exclusive way.
There are a number of libraries and blog posts describing how to implement a DLM (Distributed Lock Manager) with Redis, but every library uses a different approach, and many use a simple approach with lower guarantees compared to what can be achieved with slightly more complex designs.
This is an implementation of a proposed distributed lock algorithm with Redis. It started as a fork from antirez implementation.
Add this line to your application's Gemfile:
gem 'redlock'
And then execute:
$ bundle
Or install it yourself as:
$ gem install redlock
# Locking
lock_manager = Redlock::Client.new([ "redis://127.0.0.1:7777", "redis://127.0.0.1:7778", "redis://127.0.0.1:7779" ])
first_try_lock_info = lock_manager.lock("resource_key", 2000)
second_try_lock_info = lock_manager.lock("resource_key", 2000)
# it prints lock info {validity: 1987, resource: "resource_key", value: "generated_uuid4"}
p first_try_lock_info
# it prints false
p second_try_lock_info
# Unlocking
lock_manager.unlock(first_try_lock_info)
second_try_lock_info = lock_manager.lock("resource_key", 2000)
# now it prints lock info
p second_try_lock_info
Redlock works seamlessly with redis sentinel, which is supported in redis 3.2+. It also allows clients to set any other arbitrary options on the Redis connection, e.g. password, driver, and more.
servers = [ 'redis://localhost:6379', Redis.new(:url => 'redis://someotherhost:6379') ]
redlock = Redlock::Client.new(servers)
There's also a block version that automatically unlocks the lock:
lock_manager.lock("resource_key", 2000) do |locked|
if locked
# critical code
else
# error handling
end
end
There's also a bang version that only executes the block if the lock is successfully acquired, returning the block's value as a result, or raising an exception otherwise:
begin
block_result = lock_manager.lock!("resource_key", 2000) do
# critical code
end
rescue Redlock::LockError
# error handling
end
Make sure you have at least 1 redis instances up.
$ rspec
This code implements an algorithm which is currently a proposal, it was not formally analyzed. Make sure to understand how it works before using it in your production environments. You can see discussion about this approach at reddit.
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request