3、Disruptor源码解读-消费者

449 阅读4分钟

上篇文章讲到,生产者通过“序号管理器”获得序号,即可写入RingBuffer的位置,把事件传递给“事件管理器”。那么消费者是如何从“事件管理器”中获取对应的事件呢?相信看完这篇,你就能获得到对应的答案。

指定事件处理器

初始化disruptor对象后,即可绑定对应的EventHandler,进行事件消费。查看源码可发现,提供了以下两种绑定方式

  • 方式1,创建BatchEventProcessor对象
public EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers{
    return createEventProcessors(new Sequence[0], handlers);
}
  • 方式2,创建WorkerPool对象
public EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers{
    return createEventProcessors(new Sequence[0], handlers);
}

那么,这两种绑定事件处理器的方式有什么区别呢?(卖个官子)

启动消费者

绑定事件处理器后,当然要启动对应的消费者了。

disruptor.start();

public RingBuffer<T> start(){
    checkOnlyStartedOnce();
    for (final ConsumerInfo consumerInfo : consumerRepository){
    consumerInfo.start(executor);
    }
    return ringBuffer;
}

查看Consumer源码能发现,每个Consumer本质都是一个java线程,内部通过while(true)的方式,不断轮训RingBuffer,既然是多线程消费RingBuffer,那么提供的两种绑定EventHandler的方法有什么区别呢?让我们先看下消费者类图

消费者类图

image.png 从类图可以很清晰的看出来,disruptor实际,提供了两种消费模式。

  • FIRST

指定多个EventHandler,每个EventHandler分配一个BatchEventProcessor,内部通过“序号管理器”,可控制当前消费的进度。

  • SECONDE

指定多个WorkHandler,每个WorkHandler最终会对应一个WorkProcessor,多个WorkProcessor通过共享“workSequence”变量,实现多线程、无冲突的消费RingBuffer(细节后后面介绍)

  • 区别

BatchEventProcessor类似广播,多个消费者之前互不干扰,各自维护各自的消费进度,同一条消息会被所有消费者消费;

WorkerPool内部维护多个WorkProcessor,通过共享“workSequenc”变量,多个WorkProcessor共同消费RingBuffer,同一条消息只能被某一个消费者消费;

那么,disruptor是如何实现上述功能的呢?

消费者流程图

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

在详解流程之前,大家可以先自己思考下,假如让你来设计RingBuffer的两种消费实现,该怎么设计呢? 想必,以下三个问题都不能被避免。


1、消费者消费的过程中,如何保证自己不超过生产者?

2、“互斥版”消费者,多个消费者之间如何高效竞争消费?加锁?无锁?

3、“广播版”消费者,多个消费者之间如何隔离消费进度,才能互不干扰?


问题一

Disruptor是通过SequenceBarrier来保证,消费者的消费进度,不会超过生产者

  • 创建“广播版”消费者
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);
    }
}

问题二

多个WorkProcessor通过共享workSequence变量来决定自己从哪开始消费,通过共享sequenceBarrier来决定自能消费的最大位置,核心方法如下

    public void run()
    {
        if (!running.compareAndSet(false, true))
        {
            throw new IllegalStateException("Thread is already running");
        }
        sequenceBarrier.clearAlert();

        notifyStart();

        boolean processedSequence = true;
        long cachedAvailableSequence = Long.MIN_VALUE;
        long nextSequence = sequence.get();
        T event = null;
        while (true)
        {
            try
            {
                if (processedSequence)
                {
                    processedSequence = false;
                    do
                    {
                        nextSequence = workSequence.get() + 1L;
                        sequence.set(nextSequence - 1L);
                    }
                    while (!workSequence.compareAndSet(nextSequence - 1L, nextSequence));
                }

                if (cachedAvailableSequence >= nextSequence)
                {
                    event = ringBuffer.get(nextSequence);
                    workHandler.onEvent(event);
                    processedSequence = true;
                }
                else
                {
                    cachedAvailableSequence = sequenceBarrier.waitFor(nextSequence);
                }
            }
            catch (final TimeoutException e)
            {
                notifyTimeout(sequence.get());
            }
            catch (final AlertException ex)
            {
                if (!running.get())
                {
                    break;
                }
            }
            catch (final Throwable ex)
            {
                // handle, mark as processed, unless the exception handler threw an exception
                exceptionHandler.handleEventException(ex, nextSequence, event);
                processedSequence = true;
            }
        }

        notifyShutdown();

        running.set(false);
    }

其实代码很简单,大家如果看不懂可以对着我上面画的流程图看下,每次循环进来,要先获得当前消费者从哪开始消费x,通过while case共享变量workSequence实现,然后向sequenceBarrier获取自己最大能消费的进度y,遍历x~y,从ringBuffer里获取对应的事件。本轮次消费完成后,重新获取x和y;

问题三

比较简单,每个BatchEventProcessor内部,自己维护消费位置进度即可

思考

通过上面分析,我们能看到,disruptor提供了两种消费模式,每种模式都是SequenceBarrier来控制消费进度,那么,如果当前生产速度过慢,消费者线程没那么忙碌,应该采用哪种“姿势”等待消息的过来呢?

消费等待策略

image.png

梳理SequenceBarrier类图可发现,默认提供了七种等待策略分别是

  • BusySpinWaitStrategy
  • LiteBlockingWaitStrategy
  • LiteTimeoutBlockingWaitStrategy
  • PhasedBackoffWaitStrategy
  • SleepingWaitStrategy
  • TimeoutBlockingWaitStrategy
  • TimeoutBlockingWaitStrategy

这里就先不展开讲了,后面分析Sequence原理的时候在介绍下

小结

简单总结下,disruptor提供了两种消费模式,分别是多线程”广播版“、多线程“互斥版”,内部都是通过Sequence和SequenceBarrie来维护当前的消费进度,可见Sequence才是RingBuffer设计的核心啊,so,下篇文章当然是讲解Sequence啦。