目录
系列索引
- Disruptor源码解析一 Disruptor高性能之道
- Disruptor源码解析二 Sequence相关类解析
- Disruptor源码解析三 RingBuffer解析
- Disruptor源码解析四 消费者的组织串联
- Disruptor源码解析五 消费者的具体实现
- Disruptor源码解析六 示例与性能压测
前言
- 前一篇章节已经具体描述了消费者的串联方式,这里主要讲解一下消费者的具体实现。
- 消费者目前的两种模式:
- 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);
}
}