Solving Product Overselling in High‑Concurrency Flash Sale Scenarios: Seven Implementation Approaches
This article analyzes the common overselling problem in high‑traffic flash‑sale systems and presents seven concrete solutions—including improved locking, AOP locking, pessimistic and optimistic locks, as well as blocking‑queue and Disruptor‑based designs—complete with SpringBoot code samples and performance testing results.
1. Introduction
High‑concurrency situations are frequent in internet companies; this article uses a product flash‑sale (seckill) scenario to simulate such conditions and provides all related code, scripts, and test cases at the end.
2. Environment
SpringBoot 2.5.7, MySQL 8.0, MybatisPlus, Swagger 2.9.2
Load testing tool: JMeter
Simulation flow: Reduce inventory → Create order → Simulate payment
3. Problem: Overselling with Simple @Transactional + Lock
The typical service method annotated with @Transactional and a lock may still cause overselling because the lock is released before the transaction commits.
@ApiOperation(value="秒杀实现方式——Lock加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
// ... business logic with lock.lock() and @Transactional
return Result.ok();
}Testing with 1,000 concurrent requests for 100 items shows overselling.
4. Solutions
4.1 Method 1 – Improved Lock (Lock before transaction)
@PostMapping("/start/lock")
public Result startLock(long skgId){
lock.lock(); // lock before transaction
try{
// business logic
}finally{
lock.unlock();
}
return Result.ok();
}Locks are held until the method finishes, preventing premature release.
4.2 Method 2 – AOP Lock
Define a custom annotation @ServiceLock and an aspect that acquires a ReentrantLock before the annotated method executes.
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceLock { String description() default ""; }
@Aspect
public class LockAspect {
private static final Lock lock = new ReentrantLock(true);
@Pointcut("@annotation(com.example.ServiceLock)")
public void lockAspect() {}
@Around("lockAspect()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
lock.lock();
try { return pjp.proceed(); }
finally { lock.unlock(); }
}
}4.3 Method 3 – Pessimistic Lock (FOR UPDATE)
Use a row‑level lock in MySQL:
@Select("SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);4.4 Method 4 – Pessimistic Lock via UPDATE
Execute an UPDATE that decrements inventory only when the row exists, effectively acquiring a table lock.
@Update("UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number>0")
int updateSecondKillById(@Param("skgId") long skgId);4.5 Method 5 – Optimistic Lock
Introduce a version column and update with a version check.
@Update("UPDATE seckill SET number=number-#{number}, version=version+1 WHERE seckill_id=#{skgId} AND version=#{version}")
int updateSecondKillByVersion(@Param("number") int number, @Param("skgId") long skgId, @Param("version") int version);Frequent update conflicts may cause many failures; not recommended for high contention.
4.6 Method 6 – Blocking Queue
Requests are placed into a bounded LinkedBlockingQueue ; a consumer thread processes them sequentially.
public class SecondKillQueue {
static final int QUEUE_MAX_SIZE = 100;
static BlockingQueue
blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
// singleton getter, produce() and consume() methods
}4.7 Method 7 – Disruptor Queue
Leverage LMAX Disruptor for ultra‑low‑latency event processing.
public class SecondKillEventProducer {
private final RingBuffer
ringBuffer;
public void secondKill(long seckillId, long userId) {
ringBuffer.publishEvent((e, seq, args) -> {
e.setSeckillId((Long) args[0]);
e.setUserId((Long) args[1]);
}, seckillId, userId);
}
}5. Summary of Findings
Methods 1 and 2 solve the lock‑before‑transaction issue by moving the lock acquisition earlier.
Methods 3‑5 rely on database‑level locking; pessimistic locks (3,4) are reliable, optimistic lock (5) performs worst under contention.
Methods 6 and 7 use queue‑based throttling; they avoid overselling but may introduce “under‑selling” when queue length equals inventory.
All seven approaches were tested under three load scenarios: 1,000 concurrent requests for 100 items, 1,000 requests for 1,000 items, and 2,000 requests for 1,000 items. Results show varying degrees of overselling or underselling depending on the method.
6. Outlook
Future work includes exploring distributed solutions for flash‑sale concurrency control.
Source code repository: https://github.com/Hofanking/springboot-second-skill-example
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.