A framework with a high concurrency of one million in seconds - Disruptor

Disruptor Introduction

    Disruptor is a high-performance concurrency framework, mainly used to create high-throughput, low-latency, lock-free data structures and event processing systems. It was originally developed by LMAX Corporation and has become a high-performance concurrent framework widely used in the industry.

Features and benefits of the Disruptor framework include:

  • High performance: The Disruptor framework can provide very high concurrent performance and throughput in a lock-free manner, such as processing millions of messages per second in a large-scale message publish and subscribe scenario.

  • Low latency: Compared with the traditional shared memory-based approach, the Disruptor framework achieves low latency through cache operations and fast message passing between threads.

  • Ease of use: The Disruptor framework provides a simple API, which can easily implement various application scenarios such as producer-consumer mode, message queue, and event processor.

  • Scalability: The Disruptor framework supports multi-threaded message processing, and the number of threads can be set according to actual needs to improve processing efficiency.

What problem does the Disruptor solve?

  • Disruptor mainly solves the concurrency problem in high-performance applications, mainly involving data caching and thread communication. In traditional concurrent programming, due to problems such as shared state and lock competition, it is easy to cause synchronization delays between threads, which affects the performance and scalability of applications.

  • Disruptor adopts lock-free concurrent programming technology, stores data in a ring buffer, and realizes concurrent reading and writing of data and communication between threads through CAS operations and other methods. While ensuring data consistency, it minimizes the synchronization overhead between threads, thereby enabling efficient message delivery and event processing. Disruptor's high throughput, low latency, and good scalability make it one of the preferred solutions for many high-concurrency applications.

Basic use of Disruptor

The following is a learning demo for the basic functions of the disruptor framework.

Import jar package

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.2.1</version>
</dependency>

Producer, consumer, event code

/**
 *
 * 功能描述: 定义事件event 通过Disruptor 进行交换的数据类型。
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2023/6/17 11:33 下午
 */
public class LogEvent {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
/**
 *
 * 功能描述: 生产者
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2023/6/18 12:01 上午
 */
public class Producer implements EventTranslator<LongEvent> {

    @Override
    public void translateTo(LongEvent event, long sequence) {
        event.setNumber(sequence);
    }
}
package com.hong.arithmetic.disruptor;

import com.lmax.disruptor.EventHandler;
/**
 *
 * 功能描述: 消费者
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2023/6/18 12:01 上午
 */
public class Consumer implements EventHandler<LongEvent> {

    @Override
    public void onEvent(LongEvent event, long sequence, boolean endOfBatch) throws Exception {
        System.out.println("Consumer:" + event.getNumber());
    }
}
package com.hong.arithmetic.disruptor;

import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
import java.util.stream.Stream;


/**
 * @author: csh
 * @Date: 2023/6/17 23:37
 * @Description:主测试方式
 */
public class DisruptorMain {
    public static void main(String[] args) {
        //开始时间
        Date start = new Date();
        //创建线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        //初始化Disruptor 其中参数顺序如下:
        // LongEvent新建
        // ringBufferSize大小一定要是2的N次方
        // executor为线程池
        Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(LongEvent::new, 1024 * 1024, executor);
        //连接消费者
        disruptor.handleEventsWith(new Consumer());
        //启动
        RingBuffer<LongEvent> ringBuffer = disruptor.start();
        //生产者
        Producer producer = new Producer();
        IntStream.range(0, 1000000)
                .parallel()
                .forEach(i -> {
                    ringBuffer.publishEvent(producer);
                });

        //关闭服务 关闭线程池
        disruptor.shutdown();
        executor.shutdown();
        Date end = new Date();
        System.out.println((end.getTime() - start.getTime()) / 1000 + "秒");
    }
}

result

...
Consumer:999847
Consumer:999848
Consumer:999849
Consumer:999850
Consumer:999851
Consumer:999852
Consumer:999853
Consumer:999854
Consumer:999855
Consumer:999856
Consumer:999857
Consumer:999858
Consumer:999859
Consumer:999860
Consumer:999861
Consumer:999862
Consumer:999863
Consumer:999864
Consumer:999865
Consumer:999866
Consumer:999867
Consumer:999868
Consumer:999869
Consumer:999870
Consumer:999871
Consumer:999872
Consumer:999873
Consumer:999874
Consumer:999875
Consumer:999876
Consumer:999877
Consumer:999878
Consumer:999879
Consumer:999880
Consumer:999881
Consumer:999882
Consumer:999883
Consumer:999884
Consumer:999885
Consumer:999886
Consumer:999887
Consumer:999888
Consumer:999889
Consumer:999890
Consumer:999891
Consumer:999892
Consumer:999893
Consumer:999894
Consumer:999895
Consumer:999896
Consumer:999897
Consumer:999898
Consumer:999899
Consumer:999900
Consumer:999901
Consumer:999902
Consumer:999903
Consumer:999904
Consumer:999905
Consumer:999906
Consumer:999907
Consumer:999908
Consumer:999909
Consumer:999910
Consumer:999911
Consumer:999912
Consumer:999913
Consumer:999914
Consumer:999915
Consumer:999916
Consumer:999917
Consumer:999918
Consumer:999919
Consumer:999920
Consumer:999921
Consumer:999922
Consumer:999923
Consumer:999924
Consumer:999925
Consumer:999926
Consumer:999927
Consumer:999928
Consumer:999929
Consumer:999930
Consumer:999931
Consumer:999932
Consumer:999933
Consumer:999934
Consumer:999935
Consumer:999936
Consumer:999937
Consumer:999938
Consumer:999939
Consumer:999940
Consumer:999941
Consumer:999942
Consumer:999943
Consumer:999944
Consumer:999945
Consumer:999946
Consumer:999947
Consumer:999948
Consumer:999949
Consumer:999950
Consumer:999951
Consumer:999952
Consumer:999953
Consumer:999954
Consumer:999955
Consumer:999956
Consumer:999957
Consumer:999958
Consumer:999959
Consumer:999960
Consumer:999961
Consumer:999962
Consumer:999963
Consumer:999964
Consumer:999965
Consumer:999966
Consumer:999967
Consumer:999968
Consumer:999969
Consumer:999970
Consumer:999971
Consumer:999972
Consumer:999973
Consumer:999974
Consumer:999975
Consumer:999976
Consumer:999977
Consumer:999978
Consumer:999979
Consumer:999980
Consumer:999981
Consumer:999982
Consumer:999983
Consumer:999984
Consumer:999985
Consumer:999986
Consumer:999987
Consumer:999988
Consumer:999989
Consumer:999990
Consumer:999991
Consumer:999992
Consumer:999993
Consumer:999994
Consumer:999995
Consumer:999996
Consumer:999997
Consumer:999998
Consumer:999999
3秒

31d6002d8456fa3b99fbcebe5b32cfdf.png

3878be9f5b3278a1c393901036eb6f47.png

It took 3 seconds to test 1 million entries, and 9 seconds to 3 million entries. Of course, there is still time to join the queue. There is no business logic yet, and the performance is not bad~. Therefore, the overall performance of this framework is one million units without considering the specific business logic. Of course, my computer is M1, and a lot of software is running on it, which only reaches 330,000/s. It may be due to the limited configuration~, interested students You can test it yourself~

Disruptor's core design principles

Disruptor is a high-performance memory queue framework. It adopts some unique design principles to achieve efficient data transmission and processing by utilizing the memory model of the Java virtual machine and hardware features such as CPU cache. The following are the core design principles of the Disruptor:

  • ring buffer

The most basic data structure in Disruptor is a ring buffer through which all producers and consumers exchange data. The size of the buffer is pre-specified and can be adjusted according to actual business needs. In Disruptor, each element is an event object (Event), which is used to encapsulate the data that needs to be passed and processed.

  • memory barrier

Disruptor utilizes hardware features such as the memory model of the Java virtual machine and CPU cache to achieve efficient data transmission and processing. In order to ensure the visibility and order of data among different threads, Disruptor adopts the technology of memory barrier (Memory Barrier), and adds special instructions between different operations to realize barrier protection and reordering optimization of memory.

  • RingBuffer filling

In order to avoid competition between producers and consumers, Disruptor adopts the pre-fill technology to create a certain number of Event objects in the RingBuffer in advance, and mark the filled positions through the cursor pointer. In this way, when the producer publishes an event to the RingBuffer, the data can be directly written into the pre-allocated Event, avoiding the process of dynamically creating Event and competing locks.

  • CAS atomic operation (lock-free)

Disruptor uses CAS (Compare And Swap) atomic operations to implement read and write operations on RingBuffer to avoid traditional lock competition problems. In the Disruptor, each consumer maintains its own Sequence (serial number), which is used to indicate the position of the event it processes in the RingBuffer. The consumer continuously updates its own Sequence through the CAS operation to realize the reading and processing of the event. deal with.

  • Separate publishing and submitting apps

Disruptor introduces the "Two-Phase Commit" mode. Before the producer publishes the event to the RingBuffer, the event is encapsulated as a BatchEventProcessor, and the event is checked and waited through the Barrier. Then submit batches of events to RingBuffer for consumption by consumers. This design pattern can effectively improve the efficiency of event processing and reduce the competition between threads.

Disruptor data structure

Disruptor is a high-performance, low-latency event processing framework that uses multiple data structures to work together to ensure efficient data transmission and processing in a multi-threaded concurrent environment. The main data structures include:

  • RingBuffer: The core data structure of the Disruptor, which is used as the data structure of the queue. Events are published to the RingBuffer through the Claim Strategy, and then the Wait Strategy waits for the consumer to acquire and process the event. RingBuffer adopts the pre-allocation method, that is, the memory space is pre-allocated for each slot during initialization, which avoids the overhead and competition caused by dynamic memory allocation.

  • Sequence: Sequence is a sequence number (self-incrementing) in the Disruptor, which represents where the producer or consumer has been processed. Each producer and each consumer maintains its own Sequence value, and RingBuffer uses the Sequence value to identify each slot. Disruptor uses Sequence to implement the form of pipeline, and the different processing stages will be connected through Sequence.

  • SequenceBarrier: SequenceBarrier, referred to as the sequence fence, is used to ensure the collaboration between consumers and producers. When the consumer reads the events in the RingBuffer, it needs to wait for the events provided by the producer to be available. When the producer publishes an event, it also needs to wait for the consumer to finish processing the previous event. SequenceBarrier can provide this waiting mechanism, it can block the consumer thread until the producer publishes enough events, and it can also block the producer thread until the consumer finishes processing the previous events.

  • BatchEventProcessor: BatchEventProcessor is called an event batch processor, which is the core processor of Disruptor and is used to process events in RingBuffer. It uses a Sequence to indicate which events the consumer has processed. When new events are available, it will process a certain number of events in batches and update the Sequence value it maintains.

  • EventHandler: EventHandler is the event handler in Disruptor, which is used to process events read from RingBuffer. Each EventHandler needs to implement the onEvent method in which to write business logic.

04449794d62f2f23c57578f6e8dbf423.png

Disruptor's waiting strategy mainly has the following specific implementation classes:

  • BusySpinWaitStrategy: Busy waiting strategy, using loop checking to wait for new events, can achieve the lowest delay, but consumes a lot of CPU resources.

  • SleepingWaitStrategy: Sleeping waiting strategy, when no new event arrives, the consumer thread will go to sleep, wake up after the specified time and continue to check the RingBuffer, which can save CPU resources more than Busy Spin.

  • YieldingWaitStrategy: Yield waiting strategy. When no new event arrives, the consumer thread will suspend execution and give up CPU resources to other threads. It is suitable for medium delay scenarios.

  • BlockingWaitStrategy: Blocking waiting strategy, which will cause the consumer thread to enter the blocking state until new events are available or timeout. Suitable for higher latency scenarios, but will have an impact on system throughput.

  • LiteBlockingWaitStrategy: It is a blocking waiting strategy for non-reentrant locks. It is lighter in implementation than BlockingWaitStrategy, and it can also implement blocking waiting.

  • TimeoutBlockingWaitStrategy: Timeout waiting strategy, which will cause the consumer thread to enter the blocking state, wait for new events within the specified time, and exit if the wait times out. Compared with BlockingWaitStrategy, it has better response performance and controllable blocking time.

These specific implementation classes can be selected according to different requirements and scenarios to achieve more efficient and stable data transmission and processing effects.

Disruptor's source code learning

Disruptor class diagram

8218217c275bfa55aa8d37e5611c276c.png

Code location: com.lmax.disruptor.dsl.Disruptor

public class Disruptor<T>
{
    private final RingBuffer<T> ringBuffer; // RingBuffer对象
    private final Executor executor; // 线程池对象
    private final ConsumerRepository<T> consumerRepository = new ConsumerRepository<T>(); // 消费者仓库对象
    private final AtomicBoolean started = new AtomicBoolean(false);//多线程环境中并发访问时的安全性
    private ExceptionHandler exceptionHandler;//异常处理策略
    /**
     * 创建Disruptor对象
     *
     * @param eventFactory 事件工厂
     * @param bufferSize RingBuffer缓冲区大小
     * @param executor 线程池
     */
    public Disruptor(EventFactory<T> eventFactory, int bufferSize, Executor executor)
    {
        this.executor = executor;
        ringBuffer = new RingBuffer<T>(eventFactory, bufferSize);

        // 创建SequenceBarrier对象
        SequenceBarrier sequenceBarrier = ringBuffer.newBarrier();

        // 创建WorkerPool对象,并启动消费者线程
        WorkerPool<T> workerPool = new WorkerPool<T>(ringBuffer, sequenceBarrier, new FatalExceptionHandler(), consumerRepository.getAll());
        ringBuffer.setGatingSequences(workerPool.getWorkerSequences());
        workerPool.start(executor);
    }

    /**
     * 注册事件处理器
     *
     * @param handlers 事件处理器
     * @return EventHandlerGroup对象
     */
    public EventHandlerGroup<T> handleEventsWith(EventHandler<? super T>... handlers)
    {
        return consumerRepository.addHandlers(ringBuffer, handlers);
    }

    /**
     * 注册关联事件处理器
     *
     * @param barrierSequences SequenceBarrier序列数组
     * @param handlers 事件处理器
     * @return EventHandlerGroup对象
     */
    public EventHandlerGroup<T> handleEventsWithWorkerPool(SequenceBarrier... barrierSequences, WorkHandler<? super T>... handlers)
    {
        return consumerRepository.addWorkerPool(ringBuffer, barrierSequences, handlers);
    }

    /**
     * 注册事件处理器,每n个事件触发一次处理
     *
     * @param n 处理的事件数
     * @param handlers 事件处理器
     * @return EventHandlerGroup对象
     */
    public EventHandlerGroup<T> handleEventsWithBatchSize(int n, EventHandler<? super T>... handlers)
    {
        return consumerRepository.addBatchEventHandlers(ringBuffer, n, handlers);
    }

    /**
     * 注册事件处理器,每n个事件触发一次处理
     *
     * @param n 处理的事件数
     * @param handlers 事件处理器
     * @return EventHandlerGroup对象
     */
    public EventHandlerGroup<T> handleEventsWithBatchSize(int n, BatchEventHandler<? super T>... handlers)
    {
        return consumerRepository.addBatchEventHandlers(ringBuffer, n, handlers);
    }

    /**
     * 注册异常处理器
     *
     * @param exceptionHandler 异常处理器
     */
    public void handleExceptionsWith(ExceptionHandler<? super T> exceptionHandler)
    {
        consumerRepository.setExceptionHandler(exceptionHandler);
    }

    /**
     * 获取RingBuffer对象
     *
     * @return RingBuffer对象
     */
    public RingBuffer<T> getRingBuffer()
    {
        return ringBuffer;
    }

    /**
     * 关闭Disruptor对象
     */
    public void shutdown()
    {
        consumerRepository.removeAll();
        ringBuffer.setGatingSequences(new Sequence[0]);
    }
}

Code location: com.lmax.disruptor.RingBuffer

6e335a50f873cccf6960d0bf3a0514b6.png

//环形缓冲的核心实现类
public final class RingBuffer<T>
{
    //RingBuffer中entry数组的大小掩码,被定义为 bufferSize - 1L,用于执行快速的modulo运算。
    private final long indexMask;
    // Object数组,用于存储Event对象。
    private final Object[] entries;
    //环形缓冲区的大小,即entry数组的长度。
    private final int bufferSize;
    //实现了RingBuffer序列化的Sequencer对象。
    private final Sequencer sequencer;
    
    /**
     * 构建一个RingBuffer实例
     *
     * @param factory 工厂对象,用于创建RingBuffer对象
     * @param sequencer Sequencer对象,用于对RingBuffer进行序列化
     */
    public RingBuffer(EventFactory<T> factory, Sequencer sequencer)
    {
        this.sequencer = sequencer;
        this.bufferSize = sequencer.getBufferSize();
        this.indexMask = bufferSize - 1L;
        this.entries = new Object[sequencer.getBufferSize()];

        // 循环调用EventFactory创建初始的事件对象
        for (int i = 0; i < bufferSize; i++)
        {
            entries[i] = factory.newInstance();
        }
    }

    /**
     * 获取RingBuffer的大小
     *
     * @return bufferSize
     */
    public int getBufferSize()
    {
        return bufferSize;
    }

    /**
     * 将指定的sequence位置上的entry设置为null
     *
     * @param sequence 序列号
     */
    public void resetTo(long sequence)
    {
        this.entries[(int) (sequence & indexMask)] = null;
    }

    /**
     * 获取下一个可用的序列号
     *
     * @return 下一个可用的序列号
     */
    public long next()
    {
        return sequencer.next();
    }

    /**
     * 获取指定的可用的sequence编号
     *
     * @param n 获取的sequence的数量
     * @return 获取的sequence编号集合
     */
    public long next(int n)
    {
        return sequencer.next(n);
    }

    /**
     * 发布指定序列号的事件,通知消费者
     *
     * @param sequence 索引值
     */
    public void publish(long sequence)
    {
        sequencer.publish(sequence);
    }

    /**
     * 发布指定区间的事件,通知消费者
     *
     * @param lo 序列号起始值
     * @param hi 序列号结束值
     */
    public void publish(long lo, long hi)
    {
        sequencer.publish(lo, hi);
    }

    /**
     * 根据序列号获取对应的entry
     *
     * @param sequence 序列号
     * @return 对应的entry
     */
    @SuppressWarnings("unchecked")
    public T get(long sequence)
    {
        return (T) entries[(int) (sequence & indexMask)];
    }
    ...
}

The RingBuffer class implements the ring buffer in the Disruptor for storing event (Event) objects and providing them to consumers for processing. The following are the main properties and methods of this class:

Object[] entries: Object数组,用于存储Event对象。
long indexMask: RingBuffer中entry数组的大小掩码,被定义为bufferSize - 1L,用于执行快速的modulo运算。
int bufferSize: 环形缓冲区的大小,即entry数组的长度。
Sequencer sequencer: 实现了RingBuffer序列化的Sequencer对象。
RingBuffer(EventFactoryfactory, Sequencer sequencer): 构造函数,用于创建RingBuffer实例并初始化相应属性。
getBufferSize(): 返回RingBuffer的大小。
resetTo(long sequence): 将指定的序列号上的entry设置为null。
next(): 返回下一个可用的序列号。
next(int n): 获取指定数量的可用序列号。
publish(long sequence): 发布指定序列号的事件。
publish(long lo, long hi): 发布指定区间内的事件。
get(long sequence): 根据序列号获取对应的entry。
在Disruptor中,生产者会首先请求一个序列号(通过调用next()或next(n)方法),然后使用该序列号将事件放入RingBuffer中。消费者会根据序列号从RingBuffer中取出相应的事件,并进行处理。此过程由RingBuffer和Sequencer负责协同完成。

The source code is simple to see here, interested students can learn by themselves~

at last

    The Disruptor framework actually came out a long time ago. It was popular around 12 years ago, and many companies later borrowed this design concept. Therefore, some domestic open source concurrent frameworks or self-developed ones are based on this or reference What this framework realizes is that we often don’t understand it. Older programmers should understand it. Of course, this article is mainly used to understand the source and basic application of the Disruptor framework for subsequent design of some high-concurrency scenarios. The plan can be used for reference, and I hope to move forward together with you~ Finally, you can refer to the following literature, which is still a good reference~

references:

    Detailed Disruptor:

        https://www.jianshu.com/p/bad7b4b44e48

    Meituan - High Performance Queue - Disruptor:

        https://tech.meituan.com/2016/11/18/disruptor.html

    Disruptor, a high-performance lock-free concurrency framework, is too strong! :

        https://cloud.tencent.com/developer/article/1701690

Guess you like

Origin blog.csdn.net/qq_16498553/article/details/131278529