Implementing Distributed Locks with Redis: Commands, Java Implementations, and Lua Script Optimizations

This article explains how to build a reliable distributed lock using Redis by introducing essential Redis commands, presenting initial Java pseudocode, addressing race‑condition pitfalls, improving the design with GETSET and Lua scripts, and providing complete, production‑ready Java implementations with unlock safety fixes.

Architecture Digest
Architecture Digest
Architecture Digest
Implementing Distributed Locks with Redis: Commands, Java Implementations, and Lua Script Optimizations

Distributed locks are essential for coordinating access to shared resources in a distributed system, and Redis offers several commands that can be combined to achieve this functionality.

The key Redis commands discussed are SETNX (set if not exists), EXPIRE (set key expiration), GETSET (set new value and return old), and the scripting commands EVAL / EVALSHA for atomic Lua execution.

Using the atomicity of SETNX, a simple non‑blocking lock can be created; the article provides a Java‑style pseudocode example:

boolean tryLock(String key, int lockSeconds) {
    if (SETNX key "1" == 1) {
        EXPIRE key lockSeconds;
        return true;
    } else {
        return false;
    }
}

boolean unlock(String key) {
    DEL key;
}

However, this approach suffers from a race condition: if the client crashes after SETNX but before EXPIRE, the lock may never expire, causing a permanent deadlock.

To mitigate this, the article introduces an improved algorithm that stores the lock’s expiration timestamp as the value, checks for stale locks, and uses GETSET to atomically replace the timestamp when a stale lock is detected. A full Java implementation of this improved lock is provided:

public class RedisLock {
    private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private final StringRedisTemplate stringRedisTemplate;
    private final byte[] lockKey;
    // ... constructor and fields omitted for brevity ...
    private boolean tryLock(RedisConnection conn, int lockSeconds) throws Exception {
        long nowTime = System.currentTimeMillis();
        long expireTime = nowTime + lockSeconds * 1000 + 1000; // 1‑second tolerance
        if (conn.setNX(lockKey, longToBytes(expireTime))) {
            conn.expire(lockKey, lockSeconds);
            return true;
        } else {
            byte[] oldValue = conn.get(lockKey);
            if (oldValue != null && bytesToLong(oldValue) < nowTime) {
                byte[] oldValue2 = conn.getSet(lockKey, longToBytes(expireTime));
                if (Arrays.equals(oldValue, oldValue2)) {
                    conn.expire(lockKey, lockSeconds);
                    return true;
                }
            }
            return false;
        }
    }
    // public tryLock, tryLock with polling, unlock, and conversion helpers omitted for brevity
}

Even with this improvement, the lock acquisition still requires at least two round‑trips to Redis, and time‑synchronization between servers remains a concern.

Starting with Redis 2.6, Lua scripts can be executed atomically on the server, eliminating the gap between SETNX and EXPIRE. The article shows a concise Lua script for lock acquisition:

if (redis.call('setnx', KEYS[1], ARGV[1]) == 1) then
    redis.call('expire', KEYS[1], tonumber(ARGV[2]))
    return true
else
    return false
end

Using this script, a streamlined Java class is presented that loads the script once and reuses it for lock attempts, providing both simple and polling‑based lock methods.

public class RedisLock {
    private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private final StringRedisTemplate stringRedisTemplate;
    private final String lockKey;
    private final List<String> keys;
    private static final RedisScript<Boolean> SETNX_AND_EXPIRE_SCRIPT;
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if (redis.call('setnx', KEYS[1], ARGV[1]) == 1) then
");
        sb.append("\tredis.call('expire', KEYS[1], tonumber(ARGV[2]))
");
        sb.append("\treturn true
");
        sb.append("else
");
        sb.append("\treturn false
");
        sb.append("end");
        SETNX_AND_EXPIRE_SCRIPT = new RedisScriptImpl<>(sb.toString(), Boolean.class);
    }
    // constructor, doTryLock, tryLock, tryLock with polling, and unlock methods omitted for brevity
}

The article also identifies a serious unlock vulnerability: a simple DEL may remove another client’s lock if the original holder’s work exceeds the lock’s TTL. To fix this, a unique lock value (UUID + timestamp) is stored, and unlocking is performed via a Lua script that deletes the key only if the stored value matches.

private static final RedisScript<Boolean> DEL_IF_GET_EQUALS;
static {
    StringBuilder sb = new StringBuilder();
    sb.append("if (redis.call('get', KEYS[1]) == ARGV[1]) then
");
    sb.append("\tredis.call('del', KEYS[1])
");
    sb.append("\treturn true
");
    sb.append("else
");
    sb.append("\treturn false
");
    sb.append("end");
    DEL_IF_GET_EQUALS = new RedisScriptImpl<>(sb.toString(), Boolean.class);
}

public void unlock() {
    if (!locked) {
        throw new IllegalStateException("not locked yet!");
    }
    locked = false;
    stringRedisTemplate.execute(DEL_IF_GET_EQUALS, Collections.singletonList(lockKey), lockValue);
}

In summary, the article walks through the evolution from a naïve Redis lock to a robust, script‑based implementation that handles expiration, atomicity, and safe unlocking, providing complete Java source code for each stage.

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.

BackendLuadistributed-lock
Architecture Digest
Written by

Architecture Digest

Focusing on Java backend development, covering application architecture from top-tier internet companies (high availability, high performance, high stability), big data, machine learning, Java architecture, and other popular fields.

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.