Disruptor源码阅读

最近在用netty处理Http请求时,需要用到队列,一直听说Disruptor的RingBuffer比JDK的队列性能更好,因此准备先大概了解下实现原理;

RingBuffer

RingBuffer本质上就是个队列,它的成员变量包括:

private final Object[] entries;
protected final int bufferSize;
protected final Sequencer sequencer;

其中entries的length为bufferSize,首尾相连,形成一个环状;Sequence是Disruptor中比较重要的一个概念,后续会讲到;
Disruptor的RingBuffer实现有几个要注意的地方:

CacheLine Padding:

定义了8个long类型变量,长度为8*8=64,而通常情况下的CPU缓存行大小为64字节(可选范围为32-256),因此可以解决多核环境下的缓存行失效问题;
查看cpu一级缓存大小:

cat /sys/devices/system/cpu/cpu0/cache/index0/size
cat /sys/devices/system/cpu/cpu0/cache/index0/type
cat /sys/devices/system/cpu/cpu0/cache/index0/level

查看缓存行大小:

cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size

生产者:

RingBuffer有生产者和消费者,生产者将元素添加到队列;在具体的实现上是通过next和publish方法实现的:

  1. next方法:
public long next(){    return sequencer.next();}

可以看到具体的实现是通过MultiProducerSequencerSingleProducerSequencer的next方法实现的:

  • SingleProducerSequencer
public long next(int n)
    {
        if (n < 1)
        {
            throw new IllegalArgumentException("n must be > 0");
        }

        long nextValue = this.nextValue;

        long nextSequence = nextValue + n;
        long wrapPoint = nextSequence - bufferSize;
        long cachedGatingSequence = this.cachedValue;

        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
        {
            long minSequence;
            while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
            {
                LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
            }

            this.cachedValue = minSequence;
        }

        this.nextValue = nextSequence;

        return nextSequence;
    }

由于生产者为单线程,因此不用考虑并发,通过wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue))判断队列是否已满(消费者速度慢,导致缓存队列满);如果队列已满,则通过LockSupport.parkNanos(1L)暂停线程执行,循环判断直至可用;可以看到作者在此添加了注释,或许后续版本会采用WaitStrategy来实现;

  • MultiProducerSequencer
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;

            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;
    }

可用看到是通过cursor.compareAndSet(current, next),也就是CAS的方式来实现的;

生产者占位成功以后,通过publish方法将元素添加到队列,并更新状态,具体的逻辑如下:

SingleProducerSequencer:

    public void publish(long sequence)
    {
        cursor.set(sequence);
        waitStrategy.signalAllWhenBlocking();
    }

MultiProducerSequencer:

    public void publish(final long sequence)
    {
        setAvailable(sequence);
        waitStrategy.signalAllWhenBlocking();
    }

可以看到disruptor生产者唯一存在竞争的地方在于获取sequence;

消费者

直接上代码,见Disruptor类:

EventHandlerGroup createEventProcessors(final Sequence[] barrierSequences,
                                               final EventHandler[] eventHandlers)
    {
        checkNotStarted();

        final Sequence[] processorSequences = new Sequence[eventHandlers.length];
        final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);

        for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
        {
            final EventHandler eventHandler = eventHandlers[i];

            final BatchEventProcessor batchEventProcessor = new BatchEventProcessor(ringBuffer, barrier, eventHandler);

            if (exceptionHandler != null)
            {
                batchEventProcessor.setExceptionHandler(exceptionHandler);
            }

            consumerRepository.add(batchEventProcessor, eventHandler, barrier);
            processorSequences[i] = batchEventProcessor.getSequence();
        }

        if (processorSequences.length > 0)
        {
            consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
        }

        return new EventHandlerGroup(this, consumerRepository, processorSequences);
    }

可用看到,每个EventProcessor(默认BatchEventProcessor)都有自己的Sequence,也就是说队列中的每个消息,每个EventProcessor都要消费一次;那么当有多个Handler时,Handler的处理顺序是怎么样的?这个问题留待后面再回答;

Disruptor通过start方法,启动消费者:

public RingBuffer start()
    {
        final Sequence[] gatingSequences = consumerRepository.getLastSequenceInChain(true);
        ringBuffer.addGatingSequences(gatingSequences);

        checkOnlyStartedOnce();
        for (final ConsumerInfo consumerInfo : consumerRepository)
        {
            consumerInfo.start(executor);
        }

        return ringBuffer;
    }
public void start(final Executor executor)
{
    executor.execute(eventprocessor);
}

executor是java.util.concurrent.Executor类型的对象,根据需要可用采用JDK提供的ThreadPoolExecutor,也可以自己实现;但从上面代码也可以看出,通过executor.execute方式启动eventprocessor,也就是disruptor通常会将每个handler放在独立的线程中进行处理;但这有个问题,比如http请求,如果使用一个handler进行实现,那意味着所有http请求将由一个线程进行处理,那岂不是性能反而下降了?那disruptor的优势体现在哪呢?

继续分析代码,consumerInfo.start(executor)实际上调用的是BatchEventProcessor的run方法:

public void run()
    {
        if (!running.compareAndSet(false, true))//避免重复启动
        {
            throw new IllegalStateException("Thread is already running");
        }
        sequenceBarrier.clearAlert();//清空alerted标志

        notifyStart();//如果实现了LifecycleAware,触发onStart事件

        T event = null;
        long nextSequence = sequence.get() + 1L;//下一个消息的读索引
        try
        {
            while (true)
            {
                try
                {
                    final long availableSequence = sequenceBarrier.waitFor(nextSequence);//从队列中获取可用的消息索引,如果队列为空,则会等待,依赖于WaitStrategy

                    while (nextSequence <= availableSequence)
                    {
                        event = dataProvider.get(nextSequence);
                        eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
                        nextSequence++;
                    }

                    sequence.set(availableSequence);
                }
                catch (final TimeoutException e)
                {
                    notifyTimeout(sequence.get());
                }
                catch (final AlertException ex)
                {
                    if (!running.get())
                    {
                        break;
                    }
                }
                catch (final Throwable ex)
                {//如果发生异常,会调用exceptionHandler进行处理,流程并不会中断
                    exceptionHandler.handleEventException(ex, nextSequence, event);
                    sequence.set(nextSequence);
                    nextSequence++;
                }
            }
        }
        finally
        {
            notifyShutdown();
            running.set(false);
        }
    }

可以看到run方法是通过sequenceBarrier.waitFor(nextSequence)获取下一个可用的事件;
下面看其具体实现,见ProcessingSequenceBarrier类:

public long waitFor(final long sequence)
        throws AlertException, InterruptedException, TimeoutException
    {
        checkAlert();

        long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this);

        if (availableSequence < sequence)
        {
            return availableSequence;
        }

        return sequencer.getHighestPublishedSequence(sequence, availableSequence);
    }

这段代码中,waitStrategy出场了,waitStrategy有如下几种实现:

  • BlockingWaitStrategy
  • BusySpinWaitStrategy
  • LiteBlockingWaitStrategy
  • PhasedBackoffWaitStrategy
  • SleepingWaitStrategy
  • TimeoutBlockingWaitStrategy
  • YieldingWaitStrategy

策略比较多,就不一一看了,拿其中的BlockingWaitStrategy和BusySpinWaitStrategy为例:

public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier)
        throws AlertException, InterruptedException
    {
        long availableSequence;
        if ((availableSequence = cursorSequence.get()) < sequence)
        {
            lock.lock();
            try
            {
                while ((availableSequence = cursorSequence.get()) < sequence)
                {
                    barrier.checkAlert();
                    processorNotifyCondition.await();
                }
            }
            finally
            {
                lock.unlock();
            }
        }

        while ((availableSequence = dependentSequence.get()) < sequence)
        {
            barrier.checkAlert();
        }

        return availableSequence;
    }

可以看到BlockingWaitStrategy是通过锁的方式实现的,因此性能比较一般,但cpu使用率会比较稳定;
另外注意到这里有个变量dependentSequence,可以看到不同handler之间是存在依赖关系的,定义依赖关系的示例代码如下:

Executor executor = Executors.newFixedThreadPool(4);

Disruptor disruptor = new Disruptor(

DataEvent.FACTORY, 1024, DaemonThreadFactory.INSTANCE);

TransformingHandler handler1 = new TransformingHandler(0);

TransformingHandler handler2 = new TransformingHandler(1);

TransformingHandler handler3 = new TransformingHandler(2);

CollatingHandler collator = new CollatingHandler();

disruptor.handleEventsWith(handler1, handler2, handler3).then(collator);

disruptor.start();

BusySpinWaitStrategy实现:

 public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence, final SequenceBarrier barrier)
        throws AlertException, InterruptedException
    {
        long availableSequence;

        while ((availableSequence = dependentSequence.get()) < sequence)
        {
            barrier.checkAlert();
        }

        return availableSequence;
    }

可以看到当队列没有可消费的数据时,会一直执行循环,因此会造成CPU的使用率很高,需要特别注意;

Sequence

Disruptor中很多地方都要用到Sequence,比如生产者获取下一个可写的索引,因此特别看看它的实现是如何解决并发的:

public class Sequence extends RhsPadding
{
    private static final Unsafe UNSAFE;
    private static final long VALUE_OFFSET;//value在内存中的偏移量

    static
    {
        UNSAFE = Util.getUnsafe();
        try
        {
            VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
        }
        catch (final Exception e)
        {
            throw new RuntimeException(e);
        }
    }
 
    public Sequence(final long initialValue)
    {
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue);
    }

    /**
     * Perform an ordered write of this sequence.  The intent is
     * a Store/Store barrier between this write and any previous
     * store.
    该方法会插入StoreStore Memory Barrier,保证在该方法之前的Store操作都会提交;
     *
     * @param value The new value for the sequence.
     */
    public void set(final long value)
    {
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
    }

    /**
     * Performs a volatile write of this sequence.  The intent is
     * a Store/Store barrier between this write and any previous
     * write and a Store/Load barrier between this write and any
     * subsequent volatile read.
     * 插入StoreStore和StoreLoad Memory Barrier,保证变量在写之后会立即提交到主内存,从而保证了可见性;
     * @param value The new value for the sequence.
     */
    public void setVolatile(final long value)
    {
        UNSAFE.putLongVolatile(this, VALUE_OFFSET, value);
    }

    /**
     * Perform a compare and set operation on the sequence.
     *
     * @param expectedValue The expected current value.
     * @param newValue The value to update to.
     * @return true if the operation succeeds, false otherwise.
     */
    public boolean compareAndSet(final long expectedValue, final long newValue)
    {
        return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);
    }

    /**
     * Atomically increment the sequence by one.
     *
     * @return The value after the increment
     */
    public long incrementAndGet()
    {
        return addAndGet(1L);
    }

    /**
     * Atomically add the supplied value.
     *采用CAS方式,避免加锁
     * @param increment The value to add to the sequence.
     * @return The value after the increment.
     */
    public long addAndGet(final long increment)
    {
        long currentValue;
        long newValue;

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

        return newValue;
    }
}

源码比较长,我删除了一些不关注的方法,下面看看它的实现:
1.CacheLine Padding:15个long变量,15*8=120, 在加上对象头的长度,即使在压缩指针的情况下,也会有120+8=128个字节;
2.采用了JDK底层的Unsafe,直接访问操作系统底层:
3.采用CAS避免加锁;

结论

可以看到Disruptor采用的是类似pub-sub模式,事件会被多个消费者消费;原因在于createEventProcessors时,每个BatchEventProcessor都有单独的Sequence;
如果要避免事件被消费多次,可以采用WorkPool,一个WorkPool中的多个WorkProcessor共用一个Sequence;当然由于Disruptor为每个WorkProcessor起一个线程,因此需要注册多个WorkProcessor;
在我的项目中,根据cpu个个数,决定WorkProcessor的个数,这些WorkProcessor的逻辑都是相同的(同一个类的多个实例),从RingBuffer中抢占式获取事件进行处理;

你可能感兴趣的:(Disruptor源码阅读)