Preventing Overselling in Flash‑Sale (SecKill) Systems with a Redis Distributed Lock
This article explains how a naïve SpringBoot SecKill implementation can cause overselling, then introduces Redis commands (SETNX, EXPIRE, GETSET) and a custom RedisLock component to achieve safe distributed locking, avoid deadlocks, and ensure accurate inventory updates during high‑concurrency flash‑sale events.
The article starts with a typical flash‑sale (SecKill) scenario where a SpringBoot controller and service expose endpoints for querying product information and placing orders. A simple in‑memory implementation leads to overselling because multiple threads can decrement the same stock simultaneously.
To solve the oversell problem, the author introduces a Redis‑based distributed lock. The basic idea is to use SETNX lock-key value to acquire a lock, EXPIRE lock-key seconds (or PEXPIRE ) to set a timeout, and DEL lock-key to release it. The lock’s value stores the lock‑expiration timestamp, allowing other clients to detect and recover from deadlocks.
A reusable RedisLock component is presented:
@Component
@Slf4j
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/** Acquire lock */
public boolean lock(String key, String value) {
if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
return true;
}
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
return true;
}
}
return false;
}
/** Release lock */
public void unlock(String key, String value) {
try {
String currentValue = redisTemplate.opsForValue().get(key);
if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)) {
redisTemplate.opsForValue().getOperations().delete(key);
}
} catch (Exception e) {
log.error("【redis分布式锁】解锁异常, {}", e);
}
}
}The lock is then integrated into the SecKill service method orderProductMockDiffUser . Before checking stock, the method attempts to acquire the lock; if it fails, a SellException with a “too many users, try later” message is thrown. After the critical section (stock check, order creation, stock decrement, and stock update), the lock is released.
Finally, the article analyses the lock logic: the first SETNX provides fast acquisition; the subsequent GET and timestamp comparison detect expired locks; GETSET atomically replaces an expired lock while ensuring only one thread succeeds, thus preventing deadlocks and guaranteeing that at most one thread modifies the stock at a time.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.