Mastering Distributed Locks: Redis, Redisson, and ZooKeeper Implementations
This article explains the limitations of local locks in distributed systems and provides detailed implementations of distributed locks using Redis (SETNX, Lua scripts, Redisson) and ZooKeeper (ephemeral sequential nodes), complete with code examples and best‑practice recommendations.
Local locks such as Synchronized or ReentrantLock work only within a single JVM; in clustered deployments they become ineffective, requiring a distributed locking mechanism.
Two typical distributed lock solutions are illustrated: one based on Redis and another on ZooKeeper.
Redis Implementation
Redis provides the SETNX command (set if not exists) to acquire a lock. To avoid deadlocks caused by crashes, an expiration time must be set atomically with the lock acquisition. Redis 2.8 introduced the combined command SET key value EX seconds NX, which sets the key with an expiration in a single atomic operation.
Sample Java code using Jedis and a Lua script ensures atomic lock release by comparing a stored token.
// Based on jedis and Lua script
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() {
try {
long end = System.currentTimeMillis() + acquireTimeout;
String token = UUID.randomUUID().toString();
while (System.currentTimeMillis() < end) {
String result = jedis.set(lockKey, token, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return token;
}
Thread.sleep(100);
}
} catch (Exception e) {
log.error("acquire lock due to error", e);
}
return null;
}
@Override
public boolean release(String token) {
if (token == 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(token));
if (RELEASE_SUCCESS.equals(result)) {
log.info("release lock success, requestToken:{}", token);
return true;
}
log.info("release lock failed, requestToken:{}, result:{}", token, result);
return false;
}Redisson, a higher‑level Redis client, wraps this logic and adds features such as automatic lock renewal (the “watchdog” mechanism) and various lock types (fair, re‑entrant, read‑write, RedLock).
Redisson Usage
private void test() {
RLock lock = redissonClient.getLock("test_lock");
lock.lock(); // auto‑renewal with default 30 s watchdog
try {
// business logic
} finally {
lock.unlock();
}
}When a lock is acquired without a timeout, Redisson starts a background task that refreshes the lock’s TTL every 10 seconds until the lock is released, preventing accidental expiration during long operations.
ZooKeeper Implementation
ZooKeeper achieves distributed locking by creating an ephemeral sequential node under a designated lock directory. The client that creates the smallest node holds the lock; others watch the predecessor node and are notified when it disappears.
Key ZooKeeper commands used: create -s -e path [data] – creates an ephemeral sequential node. ls -w path – lists children and sets a watch.
Java example using the ZooKeeper API:
public class ZooKeeperDistributedLock implements Watcher {
private ZooKeeper zk;
private String locksRoot = "/locks";
private String lockNode;
private String waitNode;
private CountDownLatch latch = new CountDownLatch(1);
private CountDownLatch connectedLatch = new CountDownLatch(1);
private int sessionTimeout = 30000;
public ZooKeeperDistributedLock(String productId) throws IOException, KeeperException, InterruptedException {
zk = new ZooKeeper("192.168.189.131:2181,192.168.189.132:2181", sessionTimeout, this);
connectedLatch.await();
}
@Override
public void process(WatchedEvent event) {
if (event.getState() == KeeperState.SyncConnected) {
connectedLatch.countDown();
return;
}
if (latch != null) latch.countDown();
}
public void acquireDistributedLock() throws KeeperException, InterruptedException {
if (!tryLock()) {
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;
}
int index = locks.indexOf(lockNode.substring(locksRoot.length() + 1));
waitNode = locks.get(index - 1);
return false;
}
private void waitForLock(String node, long timeout) throws KeeperException, InterruptedException {
Stat stat = zk.exists(locksRoot + "/" + node, true);
if (stat != null) {
latch = new CountDownLatch(1);
latch.await(timeout, TimeUnit.MILLISECONDS);
latch = null;
}
}
public void unlock() {
try {
zk.delete(lockNode, -1);
zk.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}Because the lock node is ephemeral, it disappears automatically if the client crashes, preventing deadlocks.
Summary
Redis implements distributed locks by inserting a key with an expiration and optionally using Lua scripts for atomic release; it may require active spinning to acquire the lock. ZooKeeper uses temporary sequential nodes and watches, offering a more event‑driven acquisition with lower contention. The choice depends on the project’s technology stack and performance 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.
Open Source Linux
Focused on sharing Linux/Unix content, covering fundamentals, system development, network programming, automation/operations, cloud computing, and related professional knowledge.
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.
