Mastering Distributed Locks: DB, Redis, and Zookeeper Implementations Compared
This article examines the design and implementation of distributed locks using databases, Redis, and Zookeeper, outlining their core characteristics, practical code patterns, performance trade‑offs, fault‑tolerance mechanisms, and guidance on selecting the right solution for specific workloads.
Distributed Lock Overview
A distributed lock service coordinates concurrent access to shared resources across multiple nodes, providing exclusivity, fault tolerance, and dead‑lock avoidance. Typical desirable properties include re‑entrancy, high performance, and blocking semantics.
1. Database‑Based Lock
Implementation uses a table with a unique key_id column. Inserting a row acquires the lock; deleting the row releases it. Example schema:
CREATE TABLE distributed_lock (
key_id VARCHAR(255) PRIMARY KEY,
memo VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Lock acquisition (non‑blocking tryLock) can be expressed as:
-- tryLock
INSERT INTO distributed_lock (key_id, memo) VALUES (?, ?)
ON CONFLICT (key_id) DO NOTHING;
-- unlock
DELETE FROM distributed_lock WHERE key_id = ?;If a client crashes while holding the lock, the row persists and blocks other clients. A cleanup job should periodically delete rows older than a configurable timeout (e.g., 2 minutes):
DELETE FROM distributed_lock
WHERE created_at < NOW() - INTERVAL '2 minutes';Performance is limited by the underlying DB transaction throughput (typically a few hundred TPS). High availability can be achieved with replication and VIP‑based master failover, but the solution still has a single‑point‑of‑failure risk.
2. Redis‑Based Lock
Redis implements a lock with the SET command using the NX (set if not exists) and PX (expire) options. The value is a random token that identifies the lock owner.
SET key_id token NX PX 30000 # ttl = 30 secondsAcquisition and release logic:
// tryLock
String token = UUID.randomUUID().toString();
String result = redis.set(key_id, token, "NX", "PX", ttl);
if ("OK".equals(result)) {
// lock acquired
}
// unlock
String current = redis.get(key_id);
if (token.equals(current)) {
redis.del(key_id);
}Because the lock expires automatically, a crashed client will eventually free the lock. However, if the TTL expires before the client finishes, another client may acquire the lock while the first client is still operating. A common remedy is a lock‑renewal thread that extends the TTL at a fraction of the original timeout (e.g., every 1/3 of the TTL) using PEXPIRE:
while (stillHoldingLock) {
redis.pexpire(key_id, ttl);
Thread.sleep(ttl / 3);
}Redis provides high throughput (tens of thousands of ops/sec) but relies on in‑memory data; a Redis node crash can cause lock loss. Replication (master‑slave or cluster) improves availability, though asynchronous replication may still lose lock state on master failure.
3. Zookeeper‑Based Lock
Zookeeper offers strong consistency via the ZAB protocol, ephemeral nodes, and a watch mechanism. A client creates an ephemeral sequential node under a lock path; the client that owns the smallest sequence number holds the lock.
CuratorFramework client = CuratorFrameworkFactory.newClient(zookeeperUrl, retryPolicy);
client.start();
InterProcessMutex lock = new InterProcessMutex(client, "/my_lock_path");
lock.acquire(); // blocks until lock is obtained
// critical section
lock.release();Ephemeral nodes are automatically removed when the client session expires, eliminating the need for explicit TTL. Watchers notify waiting clients when the lock node is deleted, enabling immediate hand‑over.
4. Comparison
Performance : Redis (in‑memory) > Zookeeper (network round‑trip) > Database (disk‑based).
Dead‑lock handling : DB – application‑level cleanup; Redis – TTL + optional renewal; Zookeeper – ephemeral nodes.
Availability : DB – replication + VIP failover; Redis – master‑slave or cluster (usually asynchronous); Zookeeper – quorum (≥ n/2 + 1) guarantees consistency.
Lock wake‑up : Zookeeper provides native watcher notifications; DB and Redis require polling or custom mechanisms.
Choose the implementation that matches workload characteristics: use Redis for high‑throughput, low‑latency scenarios; Zookeeper when strong consistency and built‑in coordination are required; and a simple DB lock for low‑concurrency cases.
References
Design discussion: http://weizijun.cn/2016/03/17/%E8%81%8A%E4%B8%80%E8%81%8A%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E8%AE%BE%E8%AE%A1/
Example Redis lock implementation: https://github.com/luoxn28/redis-lock
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.
dbaplus Community
Enterprise-level professional community for Database, BigData, and AIOps. Daily original articles, weekly online tech talks, monthly offline salons, and quarterly XCOPS&DAMS conferences—delivered by industry experts.
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.
