Disruptor源码解析五 消费者的具体实现

目录

 

系列索引

前言

消费者的信息

消费者的创建

消费者的实现

BatchEventProcessor

WorkPool


系列索引

前言

  • 前一篇章节已经具体描述了消费者的串联方式,这里主要讲解一下消费者的具体实现。
  • 消费者目前的两种模式:
    • BatchEventProcessor   单线程批处理消费者,同一批次添加的消费者会消费每一个event
    • WorkProcessor  消费者池,一次添加多个时,每个event只会被一个process消费。

消费者的信息

Disruptor中,通过ConsumerRepository来管理所有消费者,主要维护了以下结构:

/**
 * 消费者信息仓库,将EventHandler 与 EventProcessor关联起来。
 * 传递给Disruptor的每一个EventHandler最终都会关联到一个EventProcessor。
 */
class ConsumerRepository<T> implements Iterable<ConsumerInfo> {
    /**
     * EventHandler到批量事件处理消费者信息的映射,用于信息查询
     */
    private final Map<EventHandler<?>, EventProcessorInfo<T>> eventProcessorInfoByEventHandler =
        new IdentityHashMap<>();
    /**
     * Sequence到消费者信息的映射
     * 一个消费者可能有多个Sequence{@link WorkerPool},但是一个Sequence只从属一个消费者。
     */
    private final Map<Sequence, ConsumerInfo> eventProcessorInfoBySequence =
        new IdentityHashMap<>();
    /**
     * 消费者信息列表
     */
    private final Collection<ConsumerInfo> consumerInfos = new ArrayList<>();

        
   // ....省略了方法的实现,其实都是对这3个集合的操作
}

ConsumerRepository用于维护Disruptor的所有消费者的信息,管理的集合类里主要有ConsumerInfo接口,维护了消费者信息的抽象,目前主要有两个实现类:

  • EventProcessorInfo  单事件处理器消费者信息
  • WorkerPoolInfo   线程池消费者信息对象/工作者池信息。(WorkPool整体是一个消费者,是一个多线程的消费者)

两个接口实现都基本一致,这里主要只列一下ConsumerInfo接口的定义:

/**
 * 一个消费者信息的抽象,
 */
interface ConsumerInfo
{
    /**
     * 获取消费者拥有的所有的序列,一个消费者可能有多个Sequence,消费者的消费进度由最小的Sequence决定。
     * 这里返回数组类型,其实仅仅是为了避免{@link com.lmax.disruptor.FixedSequenceGroup}。
     */
    Sequence[] getSequences();

    /**
     * 获取当前消费者持有的序列屏障。
     * 其作用详见{@link SequenceBarrier}
     *
     * {@link com.lmax.disruptor.Sequencer#newBarrier(Sequence...)}
     */
    SequenceBarrier getBarrier();

    /**
     * 当前消费者是否是消费链末端的消费者(没有后继消费者)。
     * 如果是末端的消费者,那么它就是生产者关注的消费者(网关序列)。
     */
    boolean isEndOfChain();

    /**
     * 启动消费者。
     * 主要是为每一个{@link com.lmax.disruptor.EventProcessor}创建线程,启动事件监听。
     */
    void start(Executor executor);

    /**
     * 通知消费者处理完当前事件之后,停止下来
     * 类似线程中断或任务的取消操作
     * {@link java.util.concurrent.Future#cancel(boolean)}
     * {@link Thread#interrupt()}
     */
    void halt();

    /**
     * 当我新增了后继消费者的时候,标记为不是消费者链末端的消费者
     * 生产者就不再需要对我保持关注
     */
    void markAsUsedInBarrier();

    /**
     * 消费者当前是否正在运行
     */
    boolean isRunning();
}

消费者的创建

消费者的创建是在Disruptor中创建的,一般我们调用handleEventsWith,Disruptor会默认给我们创建一个对应类型的消费者(指BatchEventProcessor或者WorkPool)。

public class Disruptor<T>{

    @SafeVarargs
    public final EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers)
    {
        return createEventProcessors(new Sequence[0], handlers);
    }


	/**
	 * 创建事件处理器(创建BatchEventProcessor消费者)
	 * @param barrierSequences 屏障sequences, {@link com.lmax.disruptor.ProcessingSequenceBarrier#dependentSequence}
	 *                            消费者的消费进度需要慢于它的前驱消费者
	 * @param eventHandlers 事件处理方法 每一个EventHandler都会被包装为{@link BatchEventProcessor}(一个独立的消费者).
	 * @return
	 */
    EventHandlerGroup<T> createEventProcessors(
        final Sequence[] barrierSequences,
        final EventHandler<? super T>[] eventHandlers)
    {
    	// 组织消费者之间的关系只能在启动之前
        checkNotStarted();

        // 收集添加的消费者的序号
        final Sequence[] processorSequences = new Sequence[eventHandlers.length];
        // 本批次消费由于添加在同一个节点之后,因此共享该屏障
        final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);

        // 创建单线程消费者(BatchEventProcessor)
        for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
        {
            final EventHandler<? super T> eventHandler = eventHandlers[i];

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

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

            // 添加到消费者信息仓库中
            consumerRepository.add(batchEventProcessor, eventHandler, barrier);
            processorSequences[i] = batchEventProcessor.getSequence();
        }

        // 更新网关序列(生产者只需要关注所有的末端消费者节点的序列)
        updateGatingSequencesForNextInChain(barrierSequences, processorSequences);

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


    @SafeVarargs
    @SuppressWarnings("varargs")
    public final EventHandlerGroup<T> handleEventsWithWorkerPool(final WorkHandler<T>... workHandlers)
    {
        return createWorkerPool(new Sequence[0], workHandlers);
    }

	/**
	 * 创建一个多线程的消费者
	 *
	 * @param barrierSequences WorkPool消费者的所有前驱节点的序列,作为新消费者的依赖序列
	 * @param workHandlers 线程池中处理事件的单元,每一个都会包装为{@link com.lmax.disruptor.WorkProcessor},
	 *                        数组长度决定线程的数量。
	 *                        如果这些语法觉得有点奇怪的是正常的,为了更好的灵活性,比传数量会好一些
	 */
    EventHandlerGroup<T> createWorkerPool(
        final Sequence[] barrierSequences, final WorkHandler<? super T>[] workHandlers)
    {
        final SequenceBarrier sequenceBarrier = ringBuffer.newBarrier(barrierSequences);
        final WorkerPool<T> workerPool = new WorkerPool<>(ringBuffer, sequenceBarrier, exceptionHandler, workHandlers);


        consumerRepository.add(workerPool, sequenceBarrier);

        final Sequence[] workerSequences = workerPool.getWorkerSequences();

        updateGatingSequencesForNextInChain(barrierSequences, workerSequences);

        return new EventHandlerGroup<>(this, consumerRepository, workerSequences);
    }



    /**
	 * 启动Disruptor,其实就是启动消费者为。
	 * 为每一个EventProcessor创建一个独立的线程。
	 *
	 * 消费者关系的组织必须在启动之前。
     */
    public RingBuffer<T> start()
    {
        checkOnlyStartedOnce();
        for (final ConsumerInfo consumerInfo : consumerRepository)
        {
            consumerInfo.start(executor);
        }

        return ringBuffer;
    }
}

消费者的实现

和ConsumerInfo接口的实现方式一样,消费者的实现也有两个主要实现类

  • WorkerPool  内部包含了WorkProcessor[]消费者数组,WorkPool整体表示一个消费者,每个生产者publish的事件只会被WorkerPool里的某一个WorkProcessor消费
  • BatchEventProcessor  批量事件处理器,一个单线程的消费者(只有一个EventProcessor), 代理EventHandler,管理处理事件以外的其他事情(如:拉取事件,等待事件...)

BatchEventProcessor

这个类直接实现了上篇中的EventProcessor,这里主要分析里面的run方法:   死循环中,通过SequenceBarrier等待可消费的事件,交给代理的eventHandler处理,一次处理多个sequence。

public final class BatchEventProcessor<T> implements EventProcessor {

    /**
     * 数据提供者(RingBuffer)
     */
    private final DataProvider<T> dataProvider;
    /**
	 * 消费者依赖的屏障,用于协调该消费者与生产者/其他消费者之间的速度。
	 */
    private final SequenceBarrier sequenceBarrier;
    /**
     * 事件处理方法,真正处理事件的对象
     */
    private final EventHandler<? super T> eventHandler;
    /**
     * 消费者的消费进度
     */
    private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);

    private final TimeoutHandler timeoutHandler;
    /**
     * 批处理开始时的通知器
     */
    private final BatchStartAware batchStartAware;


     /**
	 * 暂停以后交给下一个线程继续执行是线程安全的
	 * It is ok to have another thread rerun this method after a halt().
     * 
     * @throws IllegalStateException if this object instance is already running in a thread
     */
    @Override
    public void run()
    {
    	// 原子变量,当能从IDLE切换到RUNNING状态时,前一个线程一定退出了run()
		// 具备happens-before原则,上一个线程修改的状态对于新线程是可见的。
        if (running.compareAndSet(IDLE, RUNNING))
        {
            // 检查中断、停止请求
            sequenceBarrier.clearAlert();

            // 通知已启动
            // 警告:如果抛出异常将导致线程终止,小心死锁风险。此外:notifyShutdown 也不会被调用。
            notifyStart();
            try
            {
                if (running.get() == RUNNING)
                {
                    processEvents();
                }
            }
            finally
            {
                // notifyStart调用成功才会走到这里
                notifyShutdown();
                // 在退出的时候会恢复到IDLE状态,且是原子变量,具备happens-before原则
				// 由volatile支持
                running.set(IDLE);
            }
        }
        else
        {
            // This is a little bit of guess work.  The running state could of changed to HALTED by
            // this point.  However, Java does not have compareAndExchange which is the only way
            // to get it exactly correct.
            if (running.get() == RUNNING)
            {
                throw new IllegalStateException("Thread is already running");
            }
            else
            {
                // 到这里可能是running状态,主要是lmax支持的多了,如果不允许重用,线程退出时(notifyShutdown后)不修改为idle状态,那么便不存在该问题。
                earlyExit();
            }
        }
    }

	/**
	 * 处理事件,核心逻辑
	 */
    private void processEvents()
    {
        T event = null;
        // 下一个消费的序号, -1 到 0,这个很重要,对于理解 WorkProcessor有帮助
		// -1是不需要消费的,第一个要消费的是0
        long nextSequence = sequence.get() + 1L;

        // 死循环,因此不会让出线程,需要独立的线程(每一个EventProcessor都需要独立的线程)
        while (true)
        {
            try
            {
            	// 通过屏障获取到的最大可用序号,比起自己去查询的话,类自身就简单干净一些,复用性更好
                final long availableSequence = sequenceBarrier.waitFor(nextSequence);
                if (batchStartAware != null)
                {
                	// 批量处理事件开始时发送通知
                    batchStartAware.onBatchStart(availableSequence - nextSequence + 1);
                }

                // 批量消费,由于没有其它事件处理器和我竞争序号,这些序号我都是可以消费的
                while (nextSequence <= availableSequence)
                {
                    event = dataProvider.get(nextSequence);
                    eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
                    nextSequence++;
                }

                // 更新消费进度(批量消费,每次消费只更新一次Sequence,减少性能消耗	)
                // 这里有毒:availableSequence理论上可能小于nextSequence,也就是可能是无效的,因此应该只在成功消费了事件之后更新
                sequence.set(availableSequence);
            }
            catch (final TimeoutException e)
            {
                // 等待sequence超时,进行重试
                notifyTimeout(sequence.get());
            }
            catch (final AlertException ex)
            {
                // 检查到中断/停止请求,如果发现已经不是运行状态,则退出while死循环
                if (running.get() != RUNNING)
                {
                    break;
                }
            }
            catch (final Throwable ex)
            {
            	// 警告:如果在处理异常时抛出新的异常,会导致跳出while循环,导致BatchEventProcessor停止工作,可能导致死锁
				// 而系统默认的异常处理会将其包装为RuntimeException!!!
                exceptionHandler.handleEventException(ex, nextSequence, event);

				// 成功处理异常后标记当前事件已被处理
				// 警告:如果自己实现的等待策略,抛出了TimeoutException、AlertException以外的异常,从而走到这里,将导致该sequence被跳过!
                // 从而导致数据/信号丢失!严重bug!
                // 严格的说,lmax这里的实现对于扩展并不是特别的安全, 安全一点的话,使用两个try块更加安全,
                // 一个try块负责获取availableSequence,第二个try块负责事件处理
                sequence.set(nextSequence);
                nextSequence++;
            }
        }
    }    
}

WorkPool

workPool中维护了WorkProcessor<?>[]数组,通过统一的一个workSequence,来控制即将消费的Sequence被哪一个WorkProcessor抢占消费。

public final class WorkerPool<T> {
/**
     * 消费者的进度取决于最小的Sequence。每一个WorkProcessor都有一个Sequence,根据WorkProcessor的Sequence可以得到消费者的进度。
     *
     * 1.那么问题来了,WorkerPool还带一个Sequence干嘛呢?
     * 答案:是WorkProcessor竞争通信的媒介!预分配(抢占)序号用的,竞争成功表示告诉其他workProcessor去消费下一个序号。
     * workSequence总是大于workProcessors的sequence的,因此它并不代表消费者的进度。workSequence甚至可能大于生产者的生产进度。
     *
     * WorkProcessor首先与workSequence同步,然后CAS更新workSequence (+1)。
     * 更新成功之后,workProcessor的进度处在workSequence更新之前进度上,就算有多个WorkProcessor进行了预分配,
     * 总有一个WorkProcessor的Sequence处于正确的进度。由于消费者的进度由最小的Sequence决定,
     * 因此workSequence的预分配更新并不会影响WorkerPool代表的消费者的消费进度。
     *
     * 2.预分配序号时+1的意义?
     * 保证了WorkerPool代表的消费者的进度是1格1格前进的,且尽可能的使所有的线程都在处理事件(保证执行效率)。
     *
     * 3.WorkerPool中最少有两个Sequence,WorkProcessor 和 WorkerPool各带一个。
     */
    private final Sequence workSequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);

    private final RingBuffer<T> ringBuffer;

    // 事件处理器,每一个都是一个无限循环的任务,因此都需要独立的线程
    // WorkProcessors are created to wrap each of the provided WorkHandlers
    private final WorkProcessor<?>[] workProcessors;


    /**
     * Start the worker pool processing events in sequence.
     */
    public RingBuffer<T> start(final Executor executor)
    {
        if (!started.compareAndSet(false, true))
        {
            throw new IllegalStateException("WorkerPool has already been started and cannot be restarted until halted.");
        }

        final long cursor = ringBuffer.getCursor();
        workSequence.set(cursor);

        for (WorkProcessor<?> processor : workProcessors)
        {
            processor.getSequence().set(cursor);
            executor.execute(processor);
        }

        return ringBuffer;
    }
}

消费者的具体实现还是由 WorkProcessor具体实现的,多个WorkProcess通过workSequence预分配来竞争要消费的event。

public final class WorkProcessor<T>  implements EventProcessor{

    /**
	 * 运行状态
	 */
    private final AtomicBoolean running = new AtomicBoolean(false);
    /**
     * workProcessor处理进度(上一次处理的序号)
     * 为何要是Sequence这个线程安全的对象呢?因为会被生产者线程们查询
     */
    private final Sequence sequence = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);
    /**
	 * 生产者消费者共享的数据结构,{@link WorkProcessor}之间先竞争sequence,竞争成功的那个可以消费。
	 */
    private final RingBuffer<T> ringBuffer;
    /**
     * WorkProcessor所属的消费者的屏障。
     */
    private final SequenceBarrier sequenceBarrier;
    /**
     * WorkProcessor绑定的事件处理器。
     * {@link WorkProcessor}负责跟踪和拉取事件,{@link WorkHandler}负责处理事件。
     */
    private final WorkHandler<? super T> workHandler;
    /**
	 * WorkerPool中的 workProcessor竞争通信的媒介。
	 * 详见:{@link WorkerPool#workSequence}
	 */
    private final Sequence workSequence;


    /**
     * It is ok to have another thread re-run this method after a halt().
     *
     * 暂停之后交给下一个线程运行是线程安全的
	 *
     * @throws IllegalStateException if this processor is already running
     */
    @Override
    public void run()
    {
    	// CAS操作保证只有一个线程能运行,和保证可见性(看见状态为false后于前一个线程将其设置为false)
        if (!running.compareAndSet(false, true))
        {
            throw new IllegalStateException("Thread is already running");
        }
        // 清除特定状态(可理解为清除线程的中断状态)
        sequenceBarrier.clearAlert();

        notifyStart();

        // 是否处理了一个事件。在处理完一个事件之后会再次竞争序号进行消费
        boolean processedSequence = true;
        // 看见的已发布序号的缓存,这里是局部变量,在该变量上无竞争
        long cachedAvailableSequence = Long.MIN_VALUE;
        // 下一个要消费的序号(要消费的事件编号),注意起始为-1 ,注意与BatchEventProcessor的区别
		// BatchEventProcessor初始值为 sequence.get()+1
		// 存为local variable 还减少大量的volatile变量读,且保证本次操作过程中的一致性
        long nextSequence = sequence.get();
        // 要消费的事件对象
        T event = null;
        while (true)
        {
            try
            {
				// 如果前一个事情被成功处理了--拉取下一个序号,并将上一个序号标记为已成功处理。
				// 一般来说,这都是正确的。
				// 这可以防止当workHandler抛出异常时,Sequence跨度太大。
                if (processedSequence)
                {
                    processedSequence = false;
                    do
                    {
                        // 获取workProcessor所属的消费者的进度,与workSequence同步(感知其他消费者的进度)
                        nextSequence = workSequence.get() + 1L;
                        sequence.set(nextSequence - 1L);
                    }
                    while (!workSequence.compareAndSet(nextSequence - 1L, nextSequence));
                    // CAS更新workSequence的序号(预分配序号),为什么这样是安全的呢?
					// 由于消费者的进度由最小的Sequence决定,当它CAS更新workSequence之后,它代替了workSequence处在旧的进度上。
					// 就算多个workProcessor竞争,总有一个是处在正确的进度上的。因此 workSequence 的更新并不会影响WorkerPool代表的消费者的消费进度。
                }

				// 它只能保证竞争到的序号是可用的,因此只能只消费一个。
				// 而BatchEventProcessor看见的所有序号都是可用的
                if (cachedAvailableSequence >= nextSequence)
                {
                    event = ringBuffer.get(nextSequence);
                    workHandler.onEvent(event);
                    processedSequence = true;
                }
                else
                {
                    // 等待生产者进行生产,这里和BatchEventProcessor不同,
                    // 如果waitFor抛出TimeoutException、Throwable以外的异常,那么cachedAvailableSequence不会被更新,
                    // 也就不会导致nextSequence被标记为已消费!
                    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
                // 同样的警告!如果在处理异常时抛出新的异常,会导致跳出while循环,导致WorkProcessor停止工作,可能导致死锁
                // 而系统默认的异常处理会将其包装为RuntimeException!!!
                exceptionHandler.handleEventException(ex, nextSequence, event);

                // 成功处理异常后标记当前事件已被消费
                processedSequence = true;
            }
        }

        notifyShutdown();

        // 写volatile,插入StoreLoad屏障,保证其他线程能看见我退出前的所有操作
        running.set(false);
    }
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值