disruptor的使用和分析

disruptor:3.3.4
jdk:1.8

介紹:

disruptor是典型的生產者和消費者模式,disruptor使用RingBuffer存放數據,sequence管理生產和消費的位置(long類型,遞增),生產者要生產數據的時候首先向生產者請求一個sequence,然後在sequence的位置放置數據。

入門:

生產的數據模型

public class LogEvent {

    private long value;

    public void setValue(long value) {
        this.value = value;
    }

    public String toString() {
        return this.value + "";
    }
}

消費者消費數據

public class EventHandler {

    public void handleEvent(LogEvent event, long sequence, boolean falg) throws Exception {
        System.out.println("handle event " + event + " cur thread " + Thread.currentThread().getId());
    }
}

初始化:

void sigleProducerSingleConsume() throws Exception {
        int bufferSize = 8; // RingBuffer的大小,2的n次方
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        EventHandler handler = new EventHandler();
        Disruptor<LogEvent> disruptor = new Disruptor<>(LogEvent::new, bufferSize, threadFactory, ProducerType.SINGLE, new BlockingWaitStrategy());
        disruptor.handleEventsWith(handler::handleEvent);
        RingBuffer<LogEvent> buffer = disruptor.start();
        for(int i=0; i<10; i++) {
            long sequence = buffer.next(); // 申請位置
            try {
                LogEvent event = buffer.get(sequence);
                event.setValue(i); // 放置數據
            } finally {
                buffer.publish(sequence); // 提交,如果不提交完成事件會一直阻塞
            }
        }
        disruptor.shutdown();
    }

原理解析:

多個生產者模式:
Disruptor<LogEvent> disruptor = new Disruptor<>(LogEvent::new, bufferSize, threadFactory, ProducerType.MULTI, new BlockingWaitStrategy());

初始化方法:

 public Disruptor(
            final EventFactory<T> eventFactory,
            final int ringBufferSize,
            final ThreadFactory threadFactory,
            final ProducerType producerType,
            final WaitStrategy waitStrategy)
    {
        this(RingBuffer.create(
                               producerType, eventFactory, ringBufferSize, waitStrategy),
                new BasicExecutor(threadFactory));
    }

創建RingBuffer:

 public static <E> RingBuffer<E> create(
        ProducerType producerType,
        EventFactory<E> factory,
        int bufferSize,
        WaitStrategy waitStrategy)
    {
        switch (producerType)
        {
            case SINGLE:
                return createSingleProducer(factory, bufferSize, waitStrategy);
            case MULTI:
                return createMultiProducer(factory, bufferSize, waitStrategy);
            default:
                throw new IllegalStateException(producerType.toString());
        }
    }
public static <E> RingBuffer<E> createMultiProducer(
        EventFactory<E> factory,
        int bufferSize,
        WaitStrategy waitStrategy)
    {
        MultiProducerSequencer sequencer = new MultiProducerSequencer(bufferSize, waitStrategy);

        return new RingBuffer<E>(factory, sequencer);
    }

這裏可以看到,使用MultiProducerSequencer 創建了RingBuffer,當我們使用單個生產者模式的時候可以看到使用SingleProducerSequencer創建了RingBuffer,所以,disruptor是使用一個sequence來維護生產者用到的index 的。

讓我們看看MultiProducerSequencer 的代碼:

public MultiProducerSequencer(int bufferSize, final WaitStrategy waitStrategy)
    {
        super(bufferSize, waitStrategy);
        availableBuffer = new int[bufferSize];
        indexMask = bufferSize - 1;
        indexShift = Util.log2(bufferSize);
        initialiseAvailableBuffer();
    }

要注意availableBuffer ,這個數組中保存了生產者在每個位置添加數據的信息。
indexShift :bufferSize >> indexShift + 1 時爲0
initialiseAvailableBuffer()初始化availableBuffer 所有元素爲-1

然後讓我們看添加數據的處理:

long sequence = buffer.next(); // 申請位置
try {
    LogEvent event = buffer.get(sequence);
    event.setValue(i); // 放置數據
} finally {
    buffer.publish(sequence); // 提交,如果不提交完成事件會一直阻塞
}

取得可用的位置

@Override
    public long next()
    {
        return next(1);
    }

    /**
     * @see Sequencer#next(int)
     */
    @Override
    public long next(int n)
    {
        if (n < 1)
        {
            throw new IllegalArgumentException("n must be > 0");
        }

        long current;
        long next;

        do
        {
            current = cursor.get(); // 
            next = current + n; // 獲取下一個可用位置,最好不要用next(n)來獲取位置,很容易產生死循環

            long wrapPoint = next - bufferSize; // 
            long cachedGatingSequence = gatingSequenceCache.get(); // 保存了消費者處理過的最小位置
        // 保證生產者沒有超過消費者一圈。
            if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current)
            {
                long gatingSequence = Util.getMinimumSequence(gatingSequences, current);

                if (wrapPoint > gatingSequence)
                {
                    LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy?
                    continue;
                }

                gatingSequenceCache.set(gatingSequence);
            }
            else if (cursor.compareAndSet(current, next))
            {
                break;
            }
        }
        while (true);

        return next;
    }

中心思想就是獲取當前可用的位置,這裏沒有用到鎖,而是使用了cas。
gatingSequence中保存了各個消費者處理過的當前位置。

這樣當前生產者就可以使用這個位置了,最後要記得publish,告知消費者去讀取數據。

    @Override
    public void publish(final long sequence)
    {
        setAvailable(sequence); // 
        waitStrategy.signalAllWhenBlocking(); // 消費者可以讀了
    }

setAvailable是一個很巧妙的設計,如果是多個生產者,availableBuffer中的值是這樣變化的:
[-1, -1, -1, -1]
[0, -1, -1, -1]
[0, 0, 0, 0]
至此,四個位置已經被寫入數據了
[1, 0, 0, 0]
[1, 1, 0, 0]
[1, 1, 0, 1]
[1, 1, 1, 1]
看第三種情形,producer1佔據了第三個位置,但是producer2先一步在第四個位置寫入了數據,consumer是可以跳過第三個位置讀取第四個位置的數據的。但是這種情況會極少出現,第三個位置沒有沒consumer讀取的話,producer2是不能拿到第四個位置的。

private void setAvailable(final long sequence)
    {
        setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence));
    }
  private int calculateAvailabilityFlag(final long sequence)
    {
        return (int) (sequence >>> indexShift);
    }

    private int calculateIndex(final long sequence)
    {
        return ((int) sequence) & indexMask;
    }
 private void setAvailableBufferValue(int index, int flag)
    {
        long bufferAddress = (index * SCALE) + BASE;
        UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章