Redis for Distributed Locking in Java Applications: SET NX PX and RedLock Implementation Guide
Use Redis SET key value NX PX with a random token and atomic Lua deletion for basic distributed locks, or implement the RedLock algorithm across multiple Redis masters for high-availability locking, as architected in the doocs/advanced-java repository.
The doocs/advanced-java repository provides comprehensive architectural guidance on implementing Redis for distributed locking in Java applications, detailing both single-instance primitives and cluster-aware algorithms. This article translates those patterns into production-ready code, demonstrating how to safely coordinate distributed JVM processes using Redis commands and the RedLock protocol described in docs/distributed-system/distributed-lock-redis-vs-zookeeper.md.
Basic Redis Lock with SET NX PX
The foundational locking mechanism relies on Redis's atomic SET command with conditional flags. According to the source documentation [25-30], this approach guarantees exclusive acquisition without race conditions.
Acquiring Locks Atomically
Use the NX (only set if Not eXists) and PX (expiration in milliseconds) flags to establish ownership with automatic deadlock prevention. This single atomic operation ensures that if a client crashes, the lock expires automatically after the specified TTL.
The Random Token Pattern
Every client must generate a unique random value (such as a UUID) to store as the lock's content. If a client holds the lock longer than the TTL, the key expires and another client may acquire it. When releasing, the original client must verify the stored value matches its token; otherwise, it would delete a lock currently owned by another process [40-45].
Atomic Release via Lua Script
Since Redis lacks a native "delete if value equals" operation, implement safe lock release using a Lua script that executes atomically on the server:
String lua =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
jedis.eval(lua, Collections.singletonList(LOCK_KEY),
Collections.singletonList(LOCK_VALUE));
This script prevents the "check-then-delete" race condition by evaluating ownership and deletion in a single server-side operation.
High-Availability with the RedLock Algorithm
For fault-tolerant locking across Redis cluster failures, the documentation describes the RedLock algorithm [54-60]. This protocol requires creating the same lock identifier on a majority of independent Redis master nodes (e.g., 3 of 5).
The client measures the total elapsed time for acquiring locks across all nodes. If this duration exceeds the lock's TTL, the attempt fails and the client rolls back by deleting keys from any nodes where acquisition succeeded. This ensures that only valid, non-expired locks are considered held.
Redis vs Zookeeper: Architectural Trade-offs
The repository provides a detailed comparison between Redis-based locks and Zookeeper coordination [335-340]:
- Redis locking requires clients to poll repeatedly using
SET … NXcommands, consuming CPU cycles and network bandwidth until the lock becomes available. - Zookeeper locking utilizes ephemeral znodes that automatically delete when the client session expires, eliminating polling overhead and TTL management concerns, though it requires maintaining persistent sessions to the Zookeeper ensemble.
Complete Java Implementation Examples
Single-Instance Lock with Jedis
The following implementation demonstrates the basic lock pattern using the Jedis client, corresponding to the architecture described in the documentation [25-30, 40-45]:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;
import java.util.UUID;
public class SimpleRedisLock {
private static final String LOCK_KEY = "my_lock";
private static final String LOCK_VALUE = UUID.randomUUID().toString();
private static final int TTL_MS = 30_000;
private final Jedis jedis = new Jedis("localhost", 6379);
public boolean tryLock() {
SetParams params = new SetParams()
.nx()
.px(TTL_MS);
String result = jedis.set(LOCK_KEY, LOCK_VALUE, params);
return "OK".equals(result);
}
public void unlock() {
String lua =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
jedis.eval(lua, Collections.singletonList(LOCK_KEY),
Collections.singletonList(LOCK_VALUE));
}
}
Cluster-Aware Locking with Redisson
For RedLock implementations across multiple masters, use the Redisson client which internally handles the algorithm described in the documentation [54-60]:
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import java.util.concurrent.TimeUnit;
public class RedLockExample {
public static void main(String[] args) throws InterruptedException {
Config cfg = new Config();
cfg.useClusterServers()
.addNodeAddress("redis://127.0.0.1:7000",
"redis://127.0.0.1:7001",
"redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(cfg);
RLock lock = redisson.getLock("order-id-12345");
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
try {
System.out.println("Lock acquired, processing order...");
} finally {
lock.unlock();
}
}
redisson.shutdown();
}
}
Zookeeper Comparison Implementation
For architectural reference, here is the Zookeeper ephemeral znode approach detailed in the documentation [67-71]:
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
public class ZkLock implements Watcher {
private ZooKeeper zk;
private final String lockPath = "/my_lock";
public ZkLock() throws Exception {
zk = new ZooKeeper("127.0.0.1:2181", 30000, this);
}
public boolean tryLock() throws KeeperException, InterruptedException {
try {
zk.create(lockPath, new byte[0],
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL);
return true;
} catch (KeeperException.NodeExistsException e) {
zk.exists(lockPath, true);
return false;
}
}
public void unlock() throws KeeperException, InterruptedException {
zk.delete(lockPath, -1);
}
@Override
public void process(WatchedEvent event) {
// Handle session events
}
}
Summary
- Use
SET … NX PXwith a random token for basic single-instance locks, ensuring automatic expiration prevents deadlocks. - Implement atomic deletion via Lua scripts to verify ownership before releasing locks, preventing accidental removal of another client's lock.
- Deploy the RedLock algorithm across multiple Redis masters (majority quorum) for high-availability scenarios where individual node failures must not compromise locking safety.
- Consider Zookeeper ephemeral znodes as an alternative when you require session-based automatic cleanup rather than TTL-based expiration and polling.
Frequently Asked Questions
Why is a random value required for Redis distributed locks?
A random token (such as a UUID) identifies the specific client instance holding the lock. If a client's operation exceeds the TTL and the lock expires, another client may acquire the same key. Without the token verification in the Lua deletion script, the first client could accidentally delete the second client's lock upon completion, violating mutual exclusion.
What is the RedLock algorithm and when should I use it?
The RedLock algorithm creates the same lock identifier on a majority of independent Redis master nodes (typically 3 of 5). It measures the total time spent acquiring these locks; if the duration exceeds the lock's TTL, the attempt fails and partial locks are rolled back. Use this when operating a Redis cluster where individual node failures must not create split-brain locking scenarios.
How does Redis locking compare to Zookeeper for distributed coordination?
Redis locks require active polling with SET … NX commands, consuming CPU and network resources until acquisition succeeds [335-337]. Zookeeper utilizes ephemeral znodes that automatically delete when the client session expires, eliminating the need for TTL management and reducing polling overhead, though it requires maintaining persistent TCP sessions to Zookeeper ensemble nodes [335-340].
Can I use SETNX and EXPIRE separately instead of SET NX PX?
No. Separate SETNX and EXPIRE commands create a race condition where the client could crash between the two commands, leaving a permanent lock without expiration. The atomic SET … NX PX operation ensures the key and its TTL are set in a single, indivisible step, which is the pattern recommended in the source documentation [25-30].
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →