Backend Development 10 min read

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.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Preventing Overselling in Flash‑Sale (SecKill) Systems with a Redis Distributed Lock

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.

JavaConcurrencyRedisDistributed LockSpringBootseckill
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.