Mastering Distributed Locks with Redis: From Basics to Redisson
This article walks through the evolution of Redis‑based distributed locks, illustrating common pitfalls and step‑by‑step improvements—from simple set‑if‑absent locks to atomic UUID checks and Lua‑scripted releases, culminating in a robust Redisson solution.
Distributed Lock Evolution
Basic principle: use a shared place (Redis, database, etc.) to acquire a lock; if successful execute the business logic, otherwise wait (often by spinning or sleeping).
Stage 1
Simple lock using setIfAbsent. If the lock is not obtained, the thread sleeps 100 ms and retries. Problem: if the process crashes after acquiring the lock, the lock is never released, causing a deadlock.
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
stringRedisTemplate.delete("lock");
return categoriesDb;
} else {
try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
return getCatalogJsonDbWithRedisLock();
}
}Solution: set an automatic expiration for the lock so it is removed even if the process fails.
Stage 2
Set expiration after acquiring the lock, but if the process crashes before calling expire, the deadlock remains.
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if (lock) {
stringRedisTemplate.expire("lock", 30, TimeUnit.SECONDS);
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
stringRedisTemplate.delete("lock");
return categoriesDb;
} else {
// retry logic …
}
}Solution: use the atomic SET key value NX EX ttl command (or the Spring wrapper) to acquire the lock and set its TTL in one step.
Stage 3
Lock and expiration are set atomically, but deleting the lock unconditionally can remove another process's lock if the original lock expires while the business logic is still running.
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111", 5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
// simulate long work
Thread.sleep(6000);
stringRedisTemplate.delete("lock");
return categoriesDb;
} else {
// retry logic …
}
}Solution: store a unique identifier (UUID) as the lock value and delete only when the stored value matches the one owned by the current thread.
Stage 4
Use a UUID and verify it before deletion, but a race condition still exists: the lock may expire after the check but before the delete, causing the deletion of another process's lock.
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
Boolean lock = ops.setIfAbsent("lock", uuid, 5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
String lockValue = ops.get("lock");
if (lockValue.equals(uuid)) {
// business logic …
stringRedisTemplate.delete("lock");
}
return categoriesDb;
} else {
// retry logic …
}
}Solution: perform the check‑and‑delete atomically with a Lua script.
Stage 5 – Final Form
public Map<String, List<Catalog2Vo>> getCatalogJsonDbWithRedisLock() {
String uuid = UUID.randomUUID().toString();
Boolean lock = ops.setIfAbsent("lock", uuid, 5, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catalog2Vo>> categoriesDb = getCategoryMap();
String lockValue = ops.get("lock");
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), lockValue);
return categoriesDb;
} else {
Thread.sleep(100);
return getCatalogJsonDbWithRedisLock();
}
}This implementation guarantees atomicity for both lock acquisition with TTL and lock release with verification; the next challenge is implementing automatic lock renewal.
Redisson
Redisson is a Java in‑memory data grid built on Redis. It provides distributed implementations of common Java objects (maps, sets, queues, locks, semaphores, atomic counters, etc.) and services such as publish/subscribe, Bloom filters, remote services, Spring cache integration, executors, and schedulers, greatly simplifying Redis usage in Java applications.
Official documentation: https://github.com/redisson/redisson/wiki
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
