Implementing Distributed Redis Locks in Spring Boot: From Synchronized Locks to Redisson
The article explains how to protect a flash‑sale inventory using Redis locks in a Spring Boot service, starting with a simple synchronized lock, moving to distributed locks with setIfAbsent, handling lock expiration issues, extending TTL, and finally simplifying the implementation with Redisson.
A senior architect presents a step‑by‑step guide for handling product flash‑sale concurrency in a Spring Boot online store by using Redis as a distributed lock provider.
1. Single‑machine lock
The inventory count is stored in Redis; each request checks the count and, within a synchronized block, decrements it to ensure atomicity.
package springbootdemo.demo.controller;
/*
* @auther 顶风少年
* @mail [email protected]
* @date 2020-01-13 11:19
* @notify
* @version 1.0
*/
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping(value = "buy")
public String get() {
synchronized (this) {
String phone = redisTemplate.opsForValue().get("phone");
Integer count = Integer.valueOf(phone);
if (count > 0) {
redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
System.out.println("抢到了" + count + "号商品");
}
return "";
}
}
}2. Distributed lock with Redis setIfAbsent
When the system is scaled to multiple instances behind Nginx, the synchronized block no longer works. A lock key is created with setIfAbsent; if the lock cannot be obtained, the request returns immediately.
package springbootdemo.demo.controller;
/* same header omitted */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping(value = "buy")
public String get() {
Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "");
if (!phoneLock) {
return "";
}
try {
String phone = redisTemplate.opsForValue().get("phone");
Integer count = Integer.valueOf(phone);
if (count > 0) {
redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
System.out.println("抢到了" + count + "号商品");
}
} finally {
redisTemplate.delete("phoneLock");
}
return "";
}
}3. Handling lock expiration
If a service crashes while holding the lock, the lock may never be released. Adding an expiration time to the lock key mitigates this, but a thread that still holds the lock after expiration may incorrectly release another thread's lock.
package springbootdemo.demo.controller;
/* same header omitted */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping(value = "buy")
public String get() {
Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
if (!phoneLock) {
return "";
}
try {
String phone = redisTemplate.opsForValue().get("phone");
Integer count = Integer.valueOf(phone);
if (count > 0) {
// simulate long processing
Thread.sleep(99999999999L);
redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
System.out.println("抢到了" + count + "号商品");
}
} finally {
redisTemplate.delete("phoneLock");
}
return "";
}
}4. Extending lock TTL
To avoid the lock expiring while the business logic is still running, a timer repeatedly refreshes the lock's TTL until the transaction finishes.
package springbootdemo.demo.controller;
/* same header omitted */
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping(value = "buy")
public String get() {
Boolean phoneLock = redisTemplate.opsForValue().setIfAbsent("phoneLock", "", 3, TimeUnit.SECONDS);
if (!phoneLock) {
return "";
}
Timer timer = null;
try {
String phone = redisTemplate.opsForValue().get("phone");
Integer count = Integer.valueOf(phone);
if (count > 0) {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
redisTemplate.opsForValue().set("phoneLock", "", 3, TimeUnit.SECONDS);
}
}, 0, 1);
redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
System.out.println("抢到了" + count + "号商品");
}
} finally {
if (timer != null) {
timer.cancel();
}
redisTemplate.delete("phoneLock");
}
return "";
}
}5. Simplifying with Redisson
Redisson provides a high‑level RLock API that automatically handles lock acquisition, expiration renewal, and release, removing the need for manual TTL management.
package springbootdemo.demo.controller;
/* same header omitted */
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedissonLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private Redisson redisson;
@GetMapping(value = "buy2")
public String get() {
RLock phoneLock = redisson.getLock("phoneLock");
phoneLock.lock(3, TimeUnit.SECONDS);
try {
String phone = redisTemplate.opsForValue().get("phone");
Integer count = Integer.valueOf(phone);
if (count > 0) {
redisTemplate.opsForValue().set("phone", String.valueOf(count - 1));
System.out.println("抢到了" + count + "号商品");
}
} finally {
phoneLock.unlock();
}
return "";
}
}The progression demonstrates how a simple synchronized block evolves into a robust distributed lock solution, ending with a concise Redisson implementation that solves the common pitfalls of lock expiration and renewal.
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.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.
