Implementing Distributed Locks with Redis, Redisson, and Zookeeper
This article explains the concepts and practical implementations of distributed locks, comparing local JVM locks with Redis‑based locks (including atomic SETNX/SETEX and Lua scripts), Redisson's high‑level API, and Zookeeper's sequential‑node approach, and provides Java code examples for each solution.
In single‑process environments, thread‑level locks such as Synchronized and ReentrantLock ensure safety, but they become insufficient when services are deployed in clusters and need a distributed coordination mechanism.
The article first introduces the idea of a distributed lock, illustrated with diagrams for local and cluster scenarios, and explains the “all‑or‑nothing” principle where only the service that successfully acquires the lock may proceed.
Redis implementation : Using the SETNX command to set a key only if it does not exist mimics a lock. The article shows how a simple SETNX can lead to dead‑locks if the process crashes before releasing the key, and why setting an expiration with SETEX alone is not enough because the two operations are not atomic. Redis 2.8+ provides the combined command SET key value NX PX milliseconds to acquire the lock and set a timeout atomically. A Java example using Jedis and a Lua script demonstrates how to acquire the lock with a random token, retry until a timeout, and release it safely by comparing the token.
private static final String LOCK_SUCCESS = "OK";
private static final Long RELEASE_SUCCESS = 1L;
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
@Override
public String acquire() {
long end = System.currentTimeMillis() + acquireTimeout;
String requireToken = UUID.randomUUID().toString();
while (System.currentTimeMillis() < end) {
String result = jedis.set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return requireToken;
}
try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
return null;
}
@Override
public boolean release(String identify) {
if (identify == null) return false;
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(identify));
return RELEASE_SUCCESS.equals(result);
}Redisson implementation : Redisson wraps Redis and offers a richer API, including automatic lock renewal (the “watchdog” mechanism) that extends the lock’s TTL while the holder is still active. The article shows how to add Redisson as a Maven dependency and use its RLock object with both the simple lock() (auto‑renew) and the timed lock(long time, TimeUnit unit) methods.
RLock lock = redissonClient.getLock("test_lock");
lock.lock(); // auto‑renewed, default 30 s
try {
// business logic
} finally {
lock.unlock();
}
// with explicit expiration
lock.lock(30, TimeUnit.SECONDS);Zookeeper implementation : Zookeeper achieves distributed locking by creating temporary sequential nodes under a common lock directory. The smallest node holds the lock; other contenders watch the node that precedes them, avoiding the “herd effect”. The article provides a full Java class that creates an EPHEMERAL_SEQUENTIAL node, determines if it is the smallest, registers a watcher on the predecessor, and deletes its node to release the lock.
public void acquireDistributedLock() {
if (tryLock()) return;
else waitForLock(waitNode, sessionTimeout);
}
private boolean tryLock() throws KeeperException, InterruptedException {
lockNode = zk.create(locksRoot + "/" + productId, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> locks = zk.getChildren(locksRoot, false);
Collections.sort(locks);
if (lockNode.equals(locksRoot + "/" + locks.get(0))) return true;
// not the smallest, watch predecessor
int previous = locks.indexOf(lockNode.substring(locksRoot.length() + 1)) - 1;
waitNode = locks.get(previous);
return false;
}Finally, the article compares the two approaches: Redis uses a simple key with expiration, which may require busy‑spinning; Zookeeper relies on watchers and sequential nodes, offering better performance under contention and automatic cleanup on server failure. The choice depends on the project’s technology stack and specific requirements.
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.
