Mastering Distributed Locks: Database, Redis, and Zookeeper Strategies

This article explains the design and implementation of distributed locks, covering database pessimistic and optimistic locks, various Redis lock patterns, Redisson and RedLock, as well as Zookeeper's lock mechanism, and compares their advantages, drawbacks, and suitable use cases.

ITPUB
ITPUB
ITPUB
Mastering Distributed Locks: Database, Redis, and Zookeeper Strategies

Introduction

Hello, I am the author. This article explores the design and implementation of distributed locks, aiming to help developers understand and apply them correctly.

Overview of distributed locks

Database‑based distributed locks

Redis‑based distributed locks

Zookeeper‑based distributed locks

Comparison of the three approaches

1. Overview of Distributed Locks

In distributed systems, scenarios such as flash‑sale ordering require a lock to prevent overselling of inventory. A distributed lock ensures mutual exclusion across multiple processes or machines sharing a critical resource.

A distributed lock controls concurrent access to shared resources in a distributed environment, guaranteeing consistency.

Common implementations include:

Database‑based locks

Redis‑based locks

Zookeeper‑based locks

2. Database‑Based Distributed Locks

2.1 Pessimistic Lock with SELECT ... FOR UPDATE

Using a SQL statement like SELECT ... FOR UPDATE can create a lock. The following simplified table stores lock information:

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=locked,0=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 lifetime in minutes',
  PRIMARY KEY (`key_resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Lock acquisition pseudo‑code (transaction required):

@Transactional
public boolean lock(String keyResource, int time) {
    ResourceLock resourceLock = select * from t_resource_lock where key_resource = #{keyResource} for update;
    try {
        if (resourceLock == null) {
            // insert lock record
            resourceLock = new ResourceLock();
            resourceLock.setTime(time);
            resourceLock.setLockFlag(1);
            resourceLock.setStatus('P'); // processing
            resourceLock.setBeginTime(new Date());
            int count = insert into resourceLock ...;
            if (count == 1) return true;
            return false;
        }
    } catch (Exception e) {
        return false;
    }
    // If lock not held and has timed out, acquire it
    if (resourceLock.getLockFlag() == '0' && 'S'.equals(resourceLock.getStatus())
        && new Date() >= resourceLock.addDateTime(resourceLock.getBeginTime(), time)) {
        resourceLock.setLockFlag(1);
        resourceLock.setStatus('P');
        resourceLock.setBeginTime(new Date());
        // update resourceLock ...
        return true;
    } else if (new Date() >= resourceLock.addDateTime(resourceLock.getBeginTime(), time)) {
        // timeout, cannot acquire
        return false;
    } else {
        return false;
    }
}

Unlock pseudo‑code:

public void unlock(String keyResource, String status) {
    resourceLock.setLockFlag(0); // release
    resourceLock.setStatus(status); // S: success, F: failure
    // update resourceLock ...
    return;
}

Overall usage pattern:

try {
    if (lock(keyResource, time)) {
        status = process(); // business logic
    }
} finally {
    unlock(keyResource, status);
}

The key points are using SELECT ... FOR UPDATE to lock the primary key, checking lock status and timeout, and always wrapping the operation in a transaction.

2.2 Optimistic Lock with a Version Field

Optimistic locking relies on a version column that increments on each update. The typical flow:

Read version and balance from the account row.

Calculate the new balance.

Attempt an UPDATE with a condition on the original version; if the version has changed, the update fails and the process retries.

SELECT version, balance FROM account WHERE user_id = '666';
-- assume oldVersion = 1
if (balance < amount) return;
left_balance = balance - amount;
UPDATE account SET balance = #{left_balance}, version = version+1
WHERE version = #{oldVersion} AND balance >= #{left_balance} AND user_id = '666';

This method is suitable for low‑contention scenarios and usually requires a retry limit.

Optimistic lock flowchart
Optimistic lock flowchart

3. Redis‑Based Distributed Locks

Common Redis lock patterns:

setnx + expire

setnx + value = expiration timestamp

SET with EX/PX and NX options

SET with unique random value + delete check

Redisson library

Redisson + RedLock algorithm

3.1 setnx + expire

if (jedis.setnx(key, lock_value) == 1) { // lock acquired
    expire(key, 100); // set TTL
    try {
        // business logic
    } finally {
        jedis.del(key); // release
    }
}

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

3.2 setnx + expiration timestamp as value

long expires = System.currentTimeMillis() + expireTime;
String expiresStr = String.valueOf(expires);
if (jedis.setnx(key, expiresStr) == 1) {
    return true; // lock acquired
}
String currentValueStr = jedis.get(key);
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
    String oldValueStr = jedis.getSet(key, expiresStr);
    if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
        return true; // lock acquired after expiration
    }
}
return false; // lock not acquired

Drawbacks: client clocks must be synchronized, no owner identifier, and possible race conditions when multiple clients expire the lock simultaneously.

3.3 SET with EX/PX and NX

if (jedis.set(key, lock_value, "NX", "EX", 100) == 1) {
    try {
        // business logic
    } finally {
        jedis.del(key);
    }
}

Issues: lock may expire before business logic finishes, and another client could delete the key unintentionally.

3.4 SET with unique request ID + safe delete

if (jedis.set(key, uni_request_id, "NX", "EX", 100) == 1) {
    try {
        // business logic
    } finally {
        if (uni_request_id.equals(jedis.get(key))) {
            jedis.del(key); // safe release
        }
    }
}

The check‑and‑delete is not atomic; a Lua script can make it atomic:

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

3.5 Redisson

Redisson adds a watchdog thread that periodically extends the lock TTL while the owning thread is alive, preventing premature expiration.

Redisson watchdog diagram
Redisson watchdog diagram

3.6 Redisson + RedLock

RedLock is a multi‑master algorithm for Redis clusters. It acquires locks on a majority of N master nodes (e.g., 3 out of 5) within a timeout, then considers the lock successful. If acquisition fails, it releases any partial locks.

Record start time in milliseconds.

Attempt to lock each master sequentially, respecting a short client‑side timeout.

If at least N/2+1 masters grant the lock and total elapsed time < lock TTL, the lock is acquired.

Adjust effective lock TTL by subtracting elapsed time.

If acquisition fails, unlock all masters to avoid stray locks.

Redisson implements this algorithm internally.

4. Zookeeper‑Based Distributed Locks

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

Persistent node

Persistent sequential node

Ephemeral node

Ephemeral sequential node

4.1 Lock Acquisition

Clients create an ephemeral sequential node under a common /locks path (e.g., lock000000001). The client that holds the smallest sequence number owns the lock. Others set a watch on the next‑lower node and wait for its deletion.

Zookeeper lock creation
Zookeeper lock creation

4.2 Lock Release

When a client finishes or crashes, its ephemeral node is automatically removed, triggering watches of the next client, which then acquires the lock.

Zookeeper lock release
Zookeeper lock release

Advantages: simple API, strong consistency. Drawbacks: high load on Zookeeper under heavy lock churn.

5. Comparison of the Three Implementations

5.1 Database Locks

Pros: Simple, no extra middleware required.

Cons: Poor performance, unsuitable for high concurrency.

5.2 Redis Locks

Pros: High performance, lightweight, strong ecosystem (e.g., Redisson).

Cons: Expiration handling is tricky; risk of accidental unlock by other clients.

5.3 Zookeeper Locks

Pros: Good reliability, mature coordination framework, libraries like Curator.

Cons: Lower performance than Redis, higher operational overhead.

5.4 Summary Table

Performance (high → low): Redis > Zookeeper ≥ Database

Implementation difficulty (low → high): Database > Redis > Zookeeper

Complexity (low → high): Zookeeper > Redis > Database

Reliability (high → low): Zookeeper > Redis > Database

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackenddatabaseconcurrencyZooKeeperDistributed Lock
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

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.