Backend Development 18 min read

Mastering Distributed Locks: Design, Implementation & DB/Redis/Zookeeper Comparison

This article explains the concepts, designs, and practical implementations of distributed locks using databases, Redis, and Zookeeper, compares their advantages and drawbacks, and provides code examples and best‑practice recommendations for reliable concurrency control in distributed systems.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Mastering Distributed Locks: Design, Implementation & DB/Redis/Zookeeper Comparison

Overview

In distributed systems, scenarios like flash sales require a distributed lock to prevent overselling inventory.

A distributed lock controls concurrent access to shared resources across multiple processes or hosts, ensuring mutual exclusion and consistency.

Common implementations include database locks, Redis locks, and Zookeeper locks.

1. Database Distributed Lock

Pessimistic Lock

Use SELECT ... FOR UPDATE within a transaction. Example table structure:

<code>CREATE TABLE `t_resource_lock` (
  `key_resource` varchar(45) NOT NULL DEFAULT '资源主键',
  `status` char(1) NOT NULL DEFAULT '' COMMENT 'S,F,P',
  `lock_flag` int unsigned NOT NULL DEFAULT '0' COMMENT '1 is locked, 0 is unlocked',
  `begin_time` datetime DEFAULT NULL COMMENT 'Start time',
  `end_time` datetime DEFAULT NULL COMMENT 'End time',
  `client_ip` varchar(45) NOT NULL DEFAULT '抢到锁的IP',
  `time` int unsigned NOT NULL DEFAULT '60' COMMENT 'Lock lifecycle in minutes',
  PRIMARY KEY (`key_resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</code>

Lock method (pseudo‑code):

<code>@Transactional
public boolean lock(String keyResource, int time) {
    // select row for update
    // if not exists, insert lock record
    // else check status and timeout
    // return true if lock acquired
}
</code>

Unlock method (pseudo‑code):

<code>public void unlock(String key, String status) {
    // set lock_flag = 0, update status
}
</code>

The flow is: try to acquire lock, execute business logic, finally release the lock.

Optimistic Lock

Based on a version field (CAS). Example flow:

Query version and balance .

Calculate new balance.

Update with condition WHERE version = oldVersion and increment version.

Suitable for low‑concurrency scenarios; requires retry logic.

2. Redis Distributed Lock

setnx + expire

setnx + value as expiration timestamp

SET with EX/PX and NX/XX options

set ex px nx + unique random value

Redisson

Redisson + RedLock

2.1 setnx + expire

<code>if (jedis.setnx(key, lockValue) == 1) {
    jedis.expire(key, 100);
    try { /* business */ } finally { jedis.del(key); }
}
</code>

Problem: lock and expiration are separate; if the process crashes after setnx but before expire , the lock may never expire.

2.2 setnx + expiration timestamp

<code>long expires = System.currentTimeMillis() + expireTime;
String expiresStr = String.valueOf(expires);
if (jedis.setnx(key, expiresStr) == 1) return true;
String current = jedis.get(key);
if (current != null && Long.parseLong(current) < System.currentTimeMillis()) {
    String old = jedis.getSet(key, expiresStr);
    if (old != null && old.equals(current)) return true;
}
return false;
</code>

Drawbacks: client clocks must be synchronized; no owner identifier, so another client may release the lock.

2.3 SET with EX/PX and NX

<code>if (jedis.set(key, lockValue, "NX", "EX", 100) == 1) {
    try { /* business */ } finally { jedis.del(key); }
}
</code>

Issues: lock may expire before business finishes; another client could delete the key.

2.4 set ex px nx + unique value

<code>if (jedis.set(key, uuid, "NX", "EX", 100) == 1) {
    try { /* business */ } finally {
        if (uuid.equals(jedis.get(key))) jedis.del(key);
    }
}
</code>

Deletion is not atomic; a Lua script can make it safe:

<code>if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
</code>

2.5 Redisson

Redisson adds a watchdog thread that periodically extends the lock’s TTL while the owning thread is alive, solving the lock‑expiration‑while‑business‑running problem.

2.6 Redisson + RedLock

RedLock uses multiple independent Redis masters (e.g., 5) to acquire a lock on a majority of nodes. Steps:

Record start time.

Try to lock each master with a short timeout.

If locks are obtained on > N/2 nodes and total time < lock TTL, the lock is considered acquired.

Adjust effective TTL by subtracting elapsed time.

If acquisition fails, unlock all masters.

3. Zookeeper Distributed Lock

Zookeeper provides four node types; the lock uses ephemeral sequential nodes .

3.1 Lock acquisition

Clients create a sequential node under a persistent /locks path. The client that owns the smallest sequential node holds the lock; others watch the next lower node.

3.2 Lock release

When the client finishes or crashes, its ephemeral node is automatically deleted, triggering the next waiting client to acquire the lock.

4. Comparison of the Three Distributed Locks

Database lock : Simple, no extra middleware, but poor performance and unsuitable for high concurrency.

Redis lock : High performance, lightweight, good framework support (Redisson); challenges include expiration handling and accidental unlocks.

Zookeeper lock : Reliable and well‑suited for coordination, but heavier and slower than Redis.

Overall ranking: Performance: Redis > Zookeeper >= Database Ease of understanding: Database > Redis > Zookeeper Implementation complexity: Zookeeper > Redis > Database Reliability: Zookeeper > Redis > Database

Redis lockconcurrencyZookeeperDistributed Lockdatabase lock
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.