Backend Development 19 min read

High-Concurrency Seckill Implementation in SpringBoot: Locking, Transaction, and Queue Strategies

This article demonstrates how to simulate high‑concurrency flash‑sale scenarios using SpringBoot, MySQL, and JMeter, and compares seven approaches—including lock‑based, AOP, pessimistic and optimistic locking, and queue‑based solutions such as BlockingQueue and Disruptor—to prevent overselling and improve performance.

Top Architect
Top Architect
Top Architect
High-Concurrency Seckill Implementation in SpringBoot: Locking, Transaction, and Queue Strategies

1. Introduction: High concurrency is common in internet companies; the article uses a flash‑sale (seckill) scenario to illustrate problems and solutions.

2. Environment: SpringBoot 2.5.7, MySQL 8.0, MybatisPlus, Swagger2.9.2; JMeter for load testing.

3. Problem: Adding @Transactional and a lock inside the service method can still cause overselling because the lock may be released before the transaction commits.

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByLock(long skgId, long userId) {
    lock.lock();
    try {
        // check stock, deduct, create order, simulate payment
    } finally {
        lock.unlock();
    }
    return Result.ok();
}

4. Solution approaches:

4.1 Lock in controller (improved): lock is acquired before the service call and released after the call, ensuring the lock lives through the transaction.

@PostMapping("/start/lock")
public Result startLock(long skgId) {
    lock.lock(); // acquire lock here
    try {
        Result result = secondKillService.startSecondKillByLock(skgId, generateUserId());
        // log result
    } finally {
        lock.unlock(); // release after transaction
    }
    return Result.ok();
}

4.2 AOP lock: a custom @ServiceLock annotation and an aspect that acquires a ReentrantLock before method execution and releases it after.

@Aspect
@Component
@Order(1)
public class LockAspect {
    private static final Lock lock = new ReentrantLock(true);

    @Pointcut("@annotation(com.scorpios.secondkill.aop.ServiceLock)")
    public void lockAspect() {}

    @Around("lockAspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        lock.lock();
        try {
            return joinPoint.proceed();
        } finally {
            lock.unlock();
        }
    }
}

4.3 Pessimistic lock (FOR UPDATE): use a SELECT … FOR UPDATE inside a @Transactional method to obtain a row lock.

@Select("SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdate(long skgId, long userId) {
    SecondKill kill = secondKillMapper.querySecondKillForUpdate(skgId);
    // deduct stock, create order, payment
    return Result.ok();
}

4.4 Table lock via UPDATE: update the stock with a conditional UPDATE statement, which acquires a table lock.

@Update("UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number>0")
int updateSecondKillById(@Param("skgId") long skgId);

public Result startSecondKillByUpdateTwo(long skgId, long userId) {
    int result = secondKillMapper.updateSecondKillById(skgId);
    if (result > 0) {
        // create order, payment
    }
    return Result.ok();
}

4.5 Optimistic lock: add a version column and update with version check; this approach may cause many update conflicts.

@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);

public Result startSecondKillByPesLock(long skgId, long userId, int number) {
    SecondKill kill = secondKillMapper.selectById(skgId);
    if (kill.getNumber() >= number) {
        int result = secondKillMapper.updateSecondKillByVersion(number, skgId, kill.getVersion());
        if (result > 0) {
            // create order, payment
        }
    }
    return Result.ok();
}

4.6 BlockingQueue: a singleton queue stores incoming requests; a consumer thread processes them sequentially, calling the AOP‑locked service method.

public class SecondKillQueue {
    static final int QUEUE_MAX_SIZE = 100;
    static BlockingQueue
blockingQueue = new LinkedBlockingQueue<>(QUEUE_MAX_SIZE);
    private SecondKillQueue() {}
    private static class SingletonHolder { private static final SecondKillQueue queue = new SecondKillQueue(); }
    public static SecondKillQueue getSkillQueue() { return SingletonHolder.queue; }
    public Boolean produce(SuccessKilled kill) { return blockingQueue.offer(kill); }
    public SuccessKilled consume() throws InterruptedException { return blockingQueue.take(); }
    public int size() { return blockingQueue.size(); }
}

4.7 Disruptor queue: a high‑performance ring buffer processes events; the producer publishes a SecondKillEvent and the consumer invokes the service.

public class DisruptorUtil {
    static Disruptor
disruptor;
    static {
        SecondKillEventFactory factory = new SecondKillEventFactory();
        int ringBufferSize = 1024;
        ThreadFactory threadFactory = r -> new Thread(r);
        disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory);
        disruptor.handleEventsWith(new SecondKillEventConsumer());
        disruptor.start();
    }
    public static void producer(SecondKillEvent kill) {
        RingBuffer
ringBuffer = disruptor.getRingBuffer();
        SecondKillEventProducer producer = new SecondKillEventProducer(ringBuffer);
        producer.secondKill(kill.getSeckillId(), kill.getUserId());
    }
}

5. Summary: The article compares seven concurrency‑control techniques—service‑level lock, controller‑level lock, AOP lock, pessimistic row lock, table lock via UPDATE, optimistic lock, and queue‑based processing (BlockingQueue and Disruptor). It highlights trade‑offs such as overselling risk, performance impact, and implementation complexity, and notes that distributed scenarios require additional strategies.

concurrencylockingSpringBootdistributedQueueseckill
Top Architect
Written by

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.

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.