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.
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.
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 acquiredDrawbacks: 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
end3.5 Redisson
Redisson adds a watchdog thread that periodically extends the lock TTL while the owning thread is alive, preventing premature expiration.
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.
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.
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
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.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
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.
