4、Disruptor源码解读-序号器

412 阅读7分钟

前面几篇文章,分别介绍了Disruptor的生产和消费逻辑,其中不乏“Sequence”的身影,生产者使用其记录生产的位置,防止出现数据覆盖;多个消费者通过其记录消费位置,可以管理自己的消费进度;消费者和生产者之间通过 SequenceBarrier保持生产和消费的平衡,即消费者不能超过生产的进度,这篇文章将会深入的分析下,Sequence到底是个什么东东。

一、温故知新

带大家再次回顾前文,看看生产者和消费者是如何借助sequencer实现生产和消费的进度管理的。

1.生产者

public <A> boolean tryPublishEvent(EventTranslatorOneArg<E, A> translator, A arg0){
    try{
        // 通过sequencer,获取自己写入的位置
        final long sequence = sequencer.tryNext();
        // 将事件写入RingBuffer
        translateAndPublish(translator, sequence, arg0);
        return true;
    }
    catch (InsufficientCapacityException e){
        return false;
    }
}

首先生产者调用tryPublishEvent方法,进行事件的发布,可以看到调用 sequencer.tryNext() 获取自己能写入的位置。

private <A> void translateAndPublish(EventTranslatorOneArg<E, A> translator, longsequence, A arg0){
    try{
        translator.translateTo(get(sequence), sequence, arg0);
    }finally{
        sequencer.publish(sequence);
    }
}

然后get(sequence)获取到自己能用的“事件传递器”,把想要发布的事件交给“事件传递器”,最后调用sequencer.publish(sequence) 发布序号。

2.消费者

EventHandlerGroup<T> createEventProcessors(final Sequence[] barrierSequences,final EventHandler<? super T>[] eventHandlers){
    ... ... 
    // SequenceBarrier是由ringBuffer内部的序号管理器Sequencer创建的
    // SequenceBarrier和生产进度代表者Sequencer就关联起来了
    final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);

    for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
    {

    // 传给BatchEventProcessor,多个BatchEventProcessor会公用一个SequenceBarrier
    final BatchEventProcessor<T> batchEventProcessor =
    new BatchEventProcessor<T>(ringBuffer, barrier, eventHandler);

    }
    ... ...
}

EventHandlerGroup<T> createWorkerPool(final Sequence[] barrierSequences, final WorkHandler<? super T>[] workHandlers){
    // 同样也是ringBuffer创建的SequenceBarrier
    final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(barrierSequences);
    final WorkerPool<T> workerPool = new WorkerPool<T>(ringBuffer, sequenceBarrier, exceptionHandler, workHandlers);
    ... ...
    return new EventHandlerGroup<T>(this, consumerRepository, workerSequences);
}
    
    
public WorkerPool(final RingBuffer<T> ringBuffer,final SequenceBarrier sequenceBarrier,final ExceptionHandler<? super T> exceptionHandler,final WorkHandler<? super T>... workHandlers){

    for (int i = 0; i < numWorkers; i++){
        // 多个workProcessors共享sequenceBarrier和workSequence
        workProcessors[i] = new WorkProcessor<T>(ringBuffer,sequenceBarrier,workHandlers[i],exceptionHandler,workSequence);
    }
}

当调用handleEventsWithWorkerPool或者handleEventsWithWorkerPool绑定事件处理器时,内部会调用 ringBuffer.newBarrier(barrierSequences)生成SequenceBarrier,SequenceBarrier内部会持有ringBuffer的序号器Sequence的引用,同时BatchEventProcessor或者BatchEventProcessor会持有刚才创建的SequenceBarrier引用,消费的过程中,通过SequenceBarrier来获取能消费的位置。

3.小结

distruptor消费者-第 3 页.drawio.png

来一张没那么标准的图来总结下,生产者和消费者通过共同维护ringbuffer的里的seq变量,从而分享生产和消费进度,同时,借助“序号屏障”来保证,消费的速度,不能超过生产的速度。

二、序号详解

那么,Sequence内部到底是如何上述功能的,不整虚的,先来点干货。

image.png

以上就是Sequencer的类图,往上看,继承了Cursored, Sequenced,分别定义了一些获取序号的方法;往下看,最终的实现类有两个,分别是SingleProducerSequencer和MultiProducerSequencer,区别在于一个是给单生产者用,一个是给多生产者用;同时AbstractSequencer持有Sequence的引用,也就是说Sequence才是整个Sequencer的核心

1.Sequence源码

Sequence可以按照AtomicLong来理解,内部借助UNSAFE类操作long变量,但是更加高效,通过允填充,消除了CPU伪共享问题

class LhsPadding
{
    protected long p1, p2, p3, p4, p5, p6, p7;
}

class Value extends LhsPadding
{
    protected volatile long value;
}

class RhsPadding extends Value
{
    protected long p9, p10, p11, p12, p13, p14, p15;
}

public class Sequence extends RhsPadding
{
    static final long INITIAL_VALUE = -1L;
    private static final Unsafe UNSAFE;
    private static final long VALUE_OFFSET;

    static
    {
        UNSAFE = Util.getUnsafe();
        try
        {
            VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
        }
        catch (final Exception e)
        {
            throw new RuntimeException(e);
        }
    }


    public Sequence()
    {
        this(INITIAL_VALUE);
    }

    public Sequence(final long initialValue)
    {
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue);
    }


    public long get()
    {
        return value;
    }


    public void set(final long value)
    {
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
    }


    public void setVolatile(final long value)
    {
        UNSAFE.putLongVolatile(this, VALUE_OFFSET, value);
    }

 
    public boolean compareAndSet(final long expectedValue, final long newValue)
    {
        return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);
    }


    public long incrementAndGet()
    {
        return addAndGet(1L);
    }


    public long addAndGet(final long increment)
    {
        long currentValue;
        long newValue;

        do
        {
            currentValue = get();
            newValue = currentValue + increment;
        }
        while (!compareAndSet(currentValue, newValue));

        return newValue;
    }

    @Override
    public String toString()
    {
        return Long.toString(get());
    }
}

2.AbstractSequencer分析

看完序号的核心逻辑,再来分析下,生产者获取序号和发布序号的具体实现,由于生产者分为两类,分别是单线程生产者和多线程生产者,对应两种序号管理机制,下面分析源码的时候,会同时罗列各自序号管理的实现,以便大家可以轻松的进行比对分析。

  • tryNext -> 获取下一个可用的生产序号
    // MultiProducerSequencer实现
    public long tryNext(int n) throws InsufficientCapacityException
    {
        if (n < 1)
        {
            throw new IllegalArgumentException("n must be > 0");
        }

        long current;
        long next;

        do
        {
            current = cursor.get();
            next = current + n;

            // 容量判断
            if (!hasAvailableCapacity(gatingSequences, n, current))
            {
                throw InsufficientCapacityException.INSTANCE;
            }
        }
        // case
        while (!cursor.compareAndSet(current, next));

        return next;
    }
    // SingleProducerSequencerPad实现
    @Override
    public long tryNext(int n) throws InsufficientCapacityException
    {
        if (n < 1)
        {
            throw new IllegalArgumentException("n must be > 0");
        }

        // 容量判断
        if (!hasAvailableCapacity(n))
        {
            throw InsufficientCapacityException.INSTANCE;
        }

        // 简单粗暴
        long nextSequence = this.nextValue += n;

        return nextSequence;
    }

通过对别可以发现,MultiProducerSequencer的tryNext方法内部通过case操作Sequence cursor变量,实现多线程生产的安全保障;SingleProducerSequencerPad实现就相对简单,有容量即可正常获取,毕竟单线程嘛;

  • hasAvailableCapacity --> 查看当前RingBuffer是否能写入
    // MultiProducerSequencer实现
    
    private boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity, long cursorValue)
    {
        // 下一位置加上所需容量减去整个bufferSize,如果为正数,那证明至少转了一圈,则需要检查gatingSequences(由消费者更新里面的Sequence值)以保证不覆盖还未被消费的 
        //由于最多只能生产不大于整个bufferSize的Events。所以减去一个bufferSize与最小sequence相比较即可

        long wrapPoint = (cursorValue + requiredCapacity) - bufferSize;
        // 当前消费者,消费的最小位置
        long cachedGatingSequence = gatingSequenceCache.get();

        //如果wrapPoint > cachedGatingSequenc说明生产者追上消费者
        //cachedGatingSequence(消费的最小进度) > cursorValue(当前写入的位置),绕了一圈了
        //因为正常来说,生产者先写,消费者后消费,cachedGatingSequence应该小于cursorValue,你只能看到我的车尾灯
        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue)
        {
            // 重新获取下消费进度最小值
            long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue);
            // 更新最小值
            gatingSequenceCache.set(minSequence);
            // 如果写入位置依然大于最小值,则不能写入
            if (wrapPoint > minSequence)
            {
                return false;
            }
        }

        return true;
    }
     // SingleProducerSequencer判断
    public boolean hasAvailableCapacity(final int requiredCapacity)
    {
        // 下一个生产Sequence位置
        long nextValue = this.nextValue;
        // 下一位置加上所需容量减去整个bufferSize,如果为正数,那证明至少转了一圈
        long wrapPoint = (nextValue + requiredCapacity) - bufferSize;
        // 当前消费者消费的位置,消费者更新这个值
        long cachedGatingSequence = this.cachedValue;

        //如果wrapPoint > cachedGatingSequenc说明生产者追上消费者
        //如果cachedGatingSequence > nextValue 说明已经绕了一圈
        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
        {
            cursor.setVolatile(nextValue);  // StoreLoad fence

            long minSequence = Util.getMinimumSequence(gatingSequences, nextValue);
            this.cachedValue = minSequence;

            if (wrapPoint > minSequence)
            {
                return false;
            }
        }

        return true;
    }

二者的区别在于,MultiProducerSequencer.hasAvailableCapacity判断能否获取到序号n,是通过case操作生成的。而SingleProducerSequencer.hasAvailableCapacity则直接使用nextValue变量即可,没有多线程安全问题。另外判断容量是否可用的时候,都用到了“gatingSequences”变量,这个变量实际创建消费者的时候,会持有消费者的消费进度的引用,所以可以通过这个数组获取到当前ringbuffer的所有消费者的消费进度,那么在生产者生产的时候,保证不超过消费者组里最小的那个进度即可。

  • publish -> 发布序号
    // MultiProducerSequencer 实现
    @Override
    public void publish(final long sequence)
    {
        setAvailable(sequence);
        waitStrategy.signalAllWhenBlocking();
    }
    // SingleProducerSequencer
    @Override
    public void publish(long sequence)
    {
        cursor.set(sequence);
        waitStrategy.signalAllWhenBlocking();
    }

SingleProducerSequencer的实现比较简单,因为是单线程生产,直接更新游标即可,同时唤醒等待的消费者(猜猜是在哪等待的呢,不要急,后面依然会有相关的分析~);MultiProducerSequencer稍微复杂点,其内部其实维护了 int[] availableBuffer变量,用来记录ringBuffer的每个槽的状态,毕竟是多线程线写。

 private void setAvailable(final long sequence)
    {
        setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence));
    }

通过calculateIndex计算出当前的sequence在availableBuffer数组中的位置,从而决定自己要占用哪个槽; calculateAvailabilityFlag则用来计算圈数,但是也就是一个标志位,暂时没看到其他作用。

   private void setAvailableBufferValue(int index, int flag)
    {
        long bufferAddress = (index * SCALE) + BASE;
        UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag);
    }

通过调用UNSAFE.putOrderedInt方法,加写屏障,功能上等价于this.availableBuffer[index] = flag,保证了对publish发布的event事件对象的更新一定先于对availableBuffer对应下标值的更新, 避免消费者拿到新的发布序列号时由于新event事件未对其可见,而错误的消费了之前老的event事件。

三、小结

本篇主要介绍了序号器在生产者端是如何更新和发布的,由于Disruptor支持两种生产模式,单线程和多线程,底层分别提供了各自的序号管理器,分析代码的时候可以比对一起看看差别。消费者需要结合“序号屏障”一起来来看,放到下篇文章详细分析~