Implementing Distributed Locks with Redis: Concepts, Algorithms, and Code Examples
This article explains how to implement distributed locks using Redis, covering the essential requirements of mutual exclusion, deadlock avoidance, and fault tolerance, detailing single‑instance and multi‑instance algorithms, code examples with SETNX and Lua scripts, and discussing challenges such as latency, crashes, and persistence.
Introduction
Distributed locks are used to solve data inconsistency problems when multiple nodes compete for the same resource in a distributed environment. Besides mutual exclusion, they must also avoid deadlocks, support reentrancy, and consider high availability and performance. Common implementations include databases, Redis, and Zookeeper.
This article focuses on implementing distributed locks with Redis and outlines three basic requirements:
Mutual exclusion – only one client can hold the lock at any given time.
No deadlock – the lock can always be acquired even if a client crashes or a network partition occurs.
Fault tolerance – as long as a majority of Redis nodes are running, clients can acquire and release the lock.
Mutual exclusion and deadlock avoidance can be achieved using Redis's SETNX command, which ensures only one node sets the lock, while an expiration time guarantees automatic release in extreme cases. Fault tolerance is achieved by deploying a Redis cluster.
Failover‑Based Redis Distributed Lock Implementation
The simplest way to use Redis for a distributed lock is to create a key with an expiration time, keep it alive for a limited period, and delete it when the client finishes.
However, in a single‑instance setup, if the master crashes or becomes unreachable, the lock may be lost. Adding a replica (master‑slave) can mitigate this, but it introduces a safety issue where the lock’s mutual exclusion can be violated during failover.
Example scenario:
Client A acquires the lock on the master node.
Before the slave synchronizes the key, the master crashes.
The slave is promoted to master.
Another client acquires the same lock on the new master – a safety violation.
Illustration:
Single‑Instance Distributed Lock
To correctly use Redis for a lock in a single instance, follow these steps: SET resource_name my_random_value NX PX 30000 This command sets the key only if it does not exist (NX) and assigns a 30‑second expiration (PX). The value must be unique across all lock requests.
To safely release the lock, use a Lua script that deletes the key only if its value matches the expected one:
if redis.call("get",KEYS[1]) == ARGV[1] then<br/> return redis.call("del",KEYS[1])<br/>else<br/> return 0<br/>endReidsLock Algorithm
In a distributed environment with N independent Redis master nodes (no replication), the lock acquisition process can be described as:
Obtain the current time in milliseconds.
Generate a lock key, a random value, and a timeout.
Send the Redis command with a short timeout to each node.
Send lock acquisition commands sequentially.
If at least N/2+1 nodes grant the lock and the elapsed time is less than the timeout, consider the lock acquired.
If the client cannot lock a majority or the effective time becomes negative, treat it as a failure and release any acquired locks.
Illustration of a 5‑node lock acquisition (client A succeeds on Redis 1, 2, and 4):
long timestamp = System.currentTimeMillis();<br/>String lockKey = xxx<br/>String value = xxx<br/>long timeout = 5000<br/>int n = 5<br/><br/>int successCount = 0;<br/>for (i = 0; i < n; i++){<br/> if(<br/> redis[i].setnx(lockKey, value, timeout)<br/> ){<br/> // set succeeded<br/> successCount++;<br/> }<br/> <br/> if(successCount == (N / 2 + 1) )<br/> break;<br/>}<br/>// total time spent<br/>long spent = System.currentTimeMillis() - timestamp<br/><br/>// success condition<br/>if(successCount == (N / 2 + 1) &&<br/> (timeout - spent) > 0<br/>){<br/> // lock acquired<br/>}else{<br/> // lock failed, need to release<br/>}Problems Faced by This Algorithm
Network latency and command execution time cause variations in expiration handling.
If a node crashes, its lock is lost and may be reacquired after restart.
Persisting lock data is required; otherwise a power loss can prevent the key from being written to disk.
…
How to Mitigate Timeout Delays?
Ensure the lock is released well before its expiration; however, guaranteeing this in practice is difficult.
How to Handle Machine Crashes?
Enable persistence so lock information is saved to disk. Redis’s expiration is time‑based, so after a restart the remaining TTL remains consistent.
What If Lock Data Is Not Persisted?
Configure Redis with fsync = always to force writes to disk, though this severely impacts performance.
To guarantee safety, a single instance must remain unavailable for longer than the maximum TTL of any lock it held, allowing the lock to become invalid and be automatically released.
Using delayed restarts can provide safety even without persistence, but may reduce availability if many instances fail simultaneously.
References
Distributed locks with Redis
Redisson
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architecture Digest
Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
