Achieving 6 Million Orders per Second with SpringBoot and LMAX Disruptor

The article explains why traditional Java BlockingQueue struggles under high load, introduces the LMAX Disruptor’s lock‑free ring buffer design, compares performance showing up to 600 万+ events per second versus 100 万 for ArrayBlockingQueue, and provides step‑by‑step SpringBoot integration with code examples and best‑practice tips.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Achieving 6 Million Orders per Second with SpringBoot and LMAX Disruptor

Many developers think of Kafka or RocketMQ when they need asynchronous decoupling and traffic shaping, but numerous scenarios only require in‑process asynchronous operations such as post‑order notifications, logging, or batch data handling. Using heavyweight distributed queues in these cases adds unnecessary maintenance cost and latency.

Traditional Java BlockingQueue implementations hit three hard limits under high concurrency: (1) heavyweight locks – ArrayBlockingQueue relies on ReentrantLock, causing massive lock contention and context‑switch overhead; (2) pseudo‑sharing – adjacent head/tail indices share a cache line, leading to frequent cache invalidation; (3) object allocation pressure – LinkedBlockingQueue creates a new node for each element, triggering frequent Young GC pauses.

The LMAX Disruptor eliminates these bottlenecks. Its core data structure is a pre‑allocated ring buffer (size must be a power of two) that provides zero‑GC object reuse and excellent cache locality. Concurrency safety is achieved without locks by using a sequence number combined with CAS operations. Padding is added around sequence variables to give each a dedicated 64‑byte cache line, eradicating false sharing. Consumers are typically single‑threaded, removing synchronization overhead while still supporting parallel, serial, or grouped consumption patterns.

Official benchmark (single‑producer, single‑consumer, pure memory forwarding) shows ArrayBlockingQueue handling roughly 1 million operations per second with microsecond latency, whereas Disruptor processes about 6 million+ operations per second with nanosecond latency. Real‑world business logic will lower the absolute numbers, but the throughput advantage remains several‑fold.

Integration with SpringBoot 3.x is straightforward. Add the Maven dependencies for spring-boot-starter-web, disruptor (v3.4.4), and Lombok. Define an OrderEvent class with fields orderId, userId, amount, eventType and a clear() method for reuse. Implement an EventFactory<OrderEvent> to pre‑create events. Create consumer handlers (e.g., OrderLogEventHandler and OrderSmsEventHandler) that implement EventHandler<OrderEvent> and contain the business logic.

@Component
@Slf4j
public class OrderLogEventHandler implements EventHandler<OrderEvent> {
    @Override
    public void onEvent(OrderEvent event, long sequence, boolean endOfBatch) {
        log.info("[订单日志] 记录订单事件:orderId={}, type={}", event.getOrderId(), event.getEventType());
        // async log, persist, etc.
    }
}

Configure Disruptor in a @Configuration class: set a power‑of‑two ring buffer size (e.g., 1024), choose a thread factory, select ProducerType.SINGLE or MULTI based on the number of producers, and pick an appropriate wait strategy (Blocking, Yielding, BusySpin, or Sleeping). Register the consumers, add a global exception handler, start the Disruptor, and expose the RingBuffer<OrderEvent> as a bean.

@Configuration
@RequiredArgsConstructor
public class DisruptorConfig {
    private final OrderLogEventHandler logEventHandler;
    private final OrderSmsEventHandler smsEventHandler;
    private static final int RING_BUFFER_SIZE = 1024;

    @Bean
    public RingBuffer<OrderEvent> orderEventRingBuffer() {
        Disruptor<OrderEvent> disruptor = new Disruptor<>(
                new OrderEventFactory(),
                RING_BUFFER_SIZE,
                r -> {
                    Thread t = new Thread(r);
                    t.setName("disruptor-order-handler");
                    t.setDaemon(true);
                    return t;
                },
                ProducerType.SINGLE,
                new BlockingWaitStrategy()
        );
        disruptor.handleEventsWith(logEventHandler, smsEventHandler);
        disruptor.handleEventsWith((e, s, eob) -> e.clear());
        disruptor.setDefaultExceptionHandler(new ExceptionHandler<OrderEvent>() {
            @Override public void handleEventException(Throwable ex, long seq, OrderEvent ev) { log.error("Disruptor消费异常, seq={}, ev={}", seq, ev, ex); }
            @Override public void handleOnStartException(Throwable ex) { log.error("Disruptor启动异常", ex); }
            @Override public void handleOnShutdownException(Throwable ex) { log.error("Disruptor关闭异常", ex); }
        });
        disruptor.start();
        Runtime.getRuntime().addShutdownHook(new Thread(disruptor::shutdown));
        return disruptor.getRingBuffer();
    }
}

The producer component obtains the next sequence, fills the pre‑created event, and publishes it. Two publishing styles are shown: a classic method that manually acquires the sequence and a lambda‑style shortcut.

@Component
@RequiredArgsConstructor
public class OrderEventProducer {
    private final RingBuffer<OrderEvent> ringBuffer;

    public void publish(Long orderId, Long userId, BigDecimal amount, String eventType) {
        long seq = ringBuffer.next();
        try {
            OrderEvent ev = ringBuffer.get(seq);
            ev.setOrderId(orderId);
            ev.setUserId(userId);
            ev.setAmount(amount);
            ev.setEventType(eventType);
        } finally {
            ringBuffer.publish(seq);
        }
    }

    public void publishEvent(Consumer<OrderEvent> consumer) {
        ringBuffer.publishEvent((e, s) -> consumer.accept(e));
    }
}

Business services invoke the producer after the core order‑creation logic, keeping the main flow synchronous while off‑loading logging, notification, and other side‑effects to Disruptor.

@Service
@RequiredArgsConstructor
@Slf4j
public class OrderService {
    private final OrderEventProducer eventProducer;

    public OrderVO createOrder(OrderCreateDTO dto) {
        Order order = doCreateOrder(dto);
        log.info("订单创建成功,orderId={}", order.getId());
        eventProducer.publish(order.getId(), order.getUserId(), order.getAmount(), "CREATE");
        return new OrderVO(order.getId());
    }

    private Order doCreateOrder(OrderCreateDTO dto) {
        return new Order(1L, dto.getUserId(), dto.getAmount());
    }
}

Consumer orchestration can be refined: serial dependencies using handleEventsWith(...).then(...), parallel groups via WorkerPool, batch processing by checking endOfBatch, and wait‑strategy selection based on latency vs CPU trade‑offs (Blocking – low CPU, moderate latency; Yielding – lower latency, moderate CPU; BusySpin – lowest latency, high CPU; Sleeping – lowest CPU, high latency variance).

// Serial chain: validate → business → notify
Disruptor.handleEventsWith(validateHandler)
        .then(businessHandler)
        .then(notifyHandler);

Key pitfalls and best‑practice recommendations include: never perform blocking I/O inside a consumer thread; size the ring buffer as a power of two and roughly 2–3× the expected peak QPS; always clear event objects after processing to avoid stale data; match ProducerType to the actual producer count; treat Disruptor strictly as an in‑process queue—not a replacement for durable MQs; and configure a global exception handler to prevent a single failure from killing the consumer thread.

Note: the 6 million figure is the theoretical maximum for pure event forwarding; real business logic will reduce throughput, but Disruptor still outperforms native queues by several times.

In summary, Disruptor fills the performance gap between JDK queues and external message brokers for scenarios demanding ultra‑low latency and high throughput within a single JVM. It offers simple SpringBoot integration, dramatic throughput gains, and flexible consumer composition, but should be adopted only when the problem truly is in‑process high‑concurrency, not as a generic substitute for distributed messaging.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaPerformanceHigh ConcurrencySpringBootDisruptor
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

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.