Implementation Principles of Distributed Locks with Redis and Zookeeper
Distributed locks synchronize multiple services across nodes, and can be implemented using Redis’s fast, AP‑oriented SET‑NX with automatic TTL renewal or Zookeeper’s CP‑oriented ephemeral sequential nodes, each offering distinct trade‑offs in performance, consistency, and suitability for various workload requirements.
Distributed locks are synchronization primitives used in distributed environments where multiple instances do not share memory. Traditional thread‑level locks (e.g., mutex , synchronized ) only guarantee safety within a single JVM, so a separate mechanism is required when services are deployed across several nodes.
The essential characteristics of a distributed lock component are:
Mutual exclusion : only one client can hold the lock at a time.
Automatic release : the lock must be released automatically if the holder crashes or loses connectivity.
Partition tolerance : the system should continue to work despite network partitions.
Most open‑source solutions implement distributed locks on top of a cluster‑capable key‑value store. The two most common approaches are based on Redis and Zookeeper . The following sections describe their principles, code examples, and trade‑offs.
Redis Implementation
Redis provides high performance and simple deployment, making it a popular choice for distributed locks. In a single‑node deployment, Redis’s single‑threaded command processing already guarantees mutual exclusion. In Sentinel or Cluster mode, each write command is routed to a single node, preserving the same property.
The core command used to acquire a lock is SET key value NX (set if not exists). A successful SET means the client has obtained the lock.
Because a client may crash, an expiration time must be set to avoid dead locks:
SET lockKey lockValue EX expireTime NXIf the lock holder crashes, the key will expire automatically. However, if the business processing time exceeds expireTime , the lock may be released prematurely, causing other clients to acquire it.
To avoid premature release, a watchdog mechanism can periodically extend the TTL. Redisson, a popular Java Redis client, implements this via a background thread (watchdog) that renews the lock before it expires.
Example of using Redisson to acquire a lock:
RLock rLock = RedissonClient.getLock("test-lock");
try {
if (rLock.tryLock()) {
// do something
}
} finally {
rLock.unlock();
}Redisson’s RLock is a re‑entrant, non‑fair lock. Its tryLock method supports three optional parameters:
waitTime : maximum time to wait for the lock. -1 means wait indefinitely.
leaseTime : lock lease duration. -1 enables the watchdog for automatic renewal; a positive value disables renewal.
unit : time unit for the previous two parameters.
The underlying implementation involves:
Submitting a Lua script that atomically creates the lock key and sets its TTL.
Using Netty’s time wheel to schedule periodic renewal tasks (default interval = internalLockLeaseTime / 3 , i.e., 10 s when the default lease is 30 s).
Handling edge cases such as network partitions, session loss, and lock expiration.
Key source snippets (simplified):
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
if (ttl == null) {
return true; // lock acquired
}
// ... wait loop with timeout and renewal logic ...
} protected void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
// register entry and start Netty timer task
Timeout task = serviceManager.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
renewExpirationAsync(threadId).whenComplete((res, e) -> {
if (res) renewExpiration(); else cancelExpirationRenewal(null);
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
entry.setTimeout(task);
}Zookeeper Implementation
Zookeeper (zk) provides a coordination service based on the ZAB (Zookeeper Atomic Broadcast) protocol, which guarantees strong consistency (CP). Distributed locks are built using ephemeral sequential nodes :
Clients create a node under a lock path (e.g., /lock/xxx-lock-0000000000 ).
The client that owns the smallest sequential node holds the lock.
Other clients watch the node that precedes theirs; when it is deleted, they re‑evaluate the smallest node.
Typical lock acquisition flow:
Client creates an EPHEMERAL_SEQUENTIAL node.
Client lists children of the lock parent and checks if its node is the smallest.
If not, it watches the immediate predecessor node.
When the predecessor is deleted, the client repeats step 2.
Sample Curator (a high‑level ZK client) code:
InterProcessMutex mutex = new InterProcessMutex(zkClient, "/lock");
try {
if (mutex.acquire(3, TimeUnit.SECONDS)) {
// lock acquired
} else {
// lock acquisition failed
}
} finally {
mutex.release();
}The underlying Curator implementation performs the same three steps: retrieve ordered children, determine if the current node is the leader, and set a watch on the predecessor if not.
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception {
// ... get sorted children, compare sequence numbers ...
if (isSmallest) {
haveTheLock = true;
} else {
client.getData().usingWatcher(watcher).forPath(previousSequencePath);
wait(millisToWait);
}
// ... cleanup on timeout or error ...
}Comparison and Selection Guidance
Both Redis and Zookeeper provide partition tolerance, but they differ in the CAP trade‑off:
Redis is an AP system (high availability, eventual consistency). It offers superior performance and is suitable for high‑throughput scenarios where occasional inconsistency is acceptable.
Zookeeper is a CP system (strong consistency, lower availability). It guarantees linearizable operations, making it ideal for scenarios where data correctness is critical.
Choosing a solution depends on business requirements:
If the workload is read‑heavy, latency‑sensitive, and can tolerate brief inconsistencies, Redis‑based locks are preferable.
If strict consistency and ordering are mandatory (e.g., configuration changes, leader election), Zookeeper‑based locks are the better fit.
In practice, many systems combine both: using Redis for fast, best‑effort locking and Zookeeper for critical coordination tasks.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
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.