上篇文章讲到,生产者通过“序号管理器”获得序号,即可写入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的方法有什么区别呢?让我们先看下消费者类图
消费者类图
从类图可以很清晰的看出来,disruptor实际,提供了两种消费模式。
- FIRST
指定多个EventHandler,每个EventHandler分配一个BatchEventProcessor,内部通过“序号管理器”,可控制当前消费的进度。
- SECONDE
指定多个WorkHandler,每个WorkHandler最终会对应一个WorkProcessor,多个WorkProcessor通过共享“workSequence”变量,实现多线程、无冲突的消费RingBuffer(细节后后面介绍)
- 区别
BatchEventProcessor类似广播,多个消费者之前互不干扰,各自维护各自的消费进度,同一条消息会被所有消费者消费;
WorkerPool内部维护多个WorkProcessor,通过共享“workSequenc”变量,多个WorkProcessor共同消费RingBuffer,同一条消息只能被某一个消费者消费;
那么,disruptor是如何实现上述功能的呢?
消费者流程图
在详解流程之前,大家可以先自己思考下,假如让你来设计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来控制消费进度,那么,如果当前生产速度过慢,消费者线程没那么忙碌,应该采用哪种“姿势”等待消息的过来呢?
消费等待策略
梳理SequenceBarrier类图可发现,默认提供了七种等待策略分别是
- BusySpinWaitStrategy
- LiteBlockingWaitStrategy
- LiteTimeoutBlockingWaitStrategy
- PhasedBackoffWaitStrategy
- SleepingWaitStrategy
- TimeoutBlockingWaitStrategy
- TimeoutBlockingWaitStrategy
这里就先不展开讲了,后面分析Sequence原理的时候在介绍下
小结
简单总结下,disruptor提供了两种消费模式,分别是多线程”广播版“、多线程“互斥版”,内部都是通过Sequence和SequenceBarrie来维护当前的消费进度,可见Sequence才是RingBuffer设计的核心啊,so,下篇文章当然是讲解Sequence啦。