Disruptor深入解读
将系统性能优化到极致,永远是程序爱好者所努力的一个方向。在java并发领域,也有很多的实践与创新,小到乐观锁、CAS,大到netty线程模型、纤程Quasar、kilim等。Disruptor是一个轻量的高性能并发框架,以惊人的吞吐量而受到广泛的关注。Disruptor为提高程序的并发性能,提供了很多新的思路,比如:
- 缓存行填充,消除伪共享;
 - RingBuffer无锁队列设计;
 - 预分配缓存对象,使用缓存的循环覆盖取代缓存的新增删除等;
 
下文将从源码角度解析Disruptor的实现原理。
1 Disruptor术语
Disruptor有很多自身的概念,使得初学者看代码会比较费劲。因此在深入Disruptor原理之前,需要先了解一下Disruptor主要的几个核心类或接口。
- Sequence: 采用缓存行填充的方式对long类型的一层包装,用以代表事件的序号。通过unsafe的cas方法从而避免了锁的开销;
 - Sequencer: 生产者与缓存RingBuffer之间的桥梁。单生产者与多生产者分别对应于两个实现SingleProducerSequencer与MultiProducerSequencer。Sequencer用于向RingBuffer申请空间,使用publish方法通过waitStrategy通知所有在等待可消费事件的SequenceBarrier;
 - WaitStrategy: WaitStrategy有多种实现,用以表示当无可消费事件时,消费者的等待策略;
 - SequenceBarrier: 消费者与缓存RingBuffer之间的桥梁。消费者并不直接访问RingBuffer,从而能减少RingBuffer上的并发冲突;
 - EventProcessor: 事件处理器,是消费者线程池Executor的调度单元,是对事件处理EventHandler与异常处理ExceptionHandler等的一层封装;
 - Event: 消费事件。Event的具体实现由用户定义;
 - RingBuffer: 基于数组的缓存实现,也是创建sequencer与定义WaitStrategy的入口;
 - Disruptor: Disruptor的使用入口。持有RingBuffer、消费者线程池Executor、消费者集合ConsumerRepository等引用。
 
2 Disruptor源码分析
2.1 Disruptor并发模型
并发领域的一个典型场景是生产者消费者模型,常规方式是使用queue作为生产者线程与消费者线程之间共享数据的方法,对于queue的读写避免不了读写锁的竞争。Disruptor使用环形缓冲区RingBuffer作为共享数据的媒介。生产者通过Sequencer控制RingBuffer,以及唤醒等待事件的消费者,消费者通过SequenceBarrier监听RingBuffer的可消费事件。考虑一个场景,一个生产者A与三个消费者B、C、D,同时D的事件处理需要B与C先完成。则该模型结构如下:

在这个结构下,每个消费者拥有各自独立的事件序号Sequence,消费者之间不存在共享竞态。SequenceBarrier1监听RingBuffer的序号cursor,消费者B与C通过SequenceBarrier1等待可消费事件。SequenceBarrier2除了监听cursor,同时也监听B与C的序号Sequence,从而将最小的序号返回给消费者D,由此实现了D依赖B与C的逻辑。
RingBuffer是Disruptor高性能的一个亮点。RingBuffer就是一个大数组,事件以循环覆盖的方式写入。与常规RingBuffer拥有2个首尾指针的方式不同,Disruptor的RingBuffer只有一个指针(或称序号),指向数组下一个可写入的位置,该序号在Disruptor源码中就是Sequencer中的cursor,由生产者通过Sequencer控制RingBuffer的写入。为了避免未消费事件的写入覆盖,Sequencer需要监听所有消费者的消息处理进度,也就是gatingSequences。RingBuffer通过这种方式实现了事件缓存的无锁设计。
下面将通过分析源码,来理解Disruptor的实现原理。
2.2 Disruptor类
Disruptor类是Disruptor框架的总入口,能用DSL的形式组织消费者之间的关系链,并提供获取事件、发布事件等方法。它包含以下属性:
private final RingBuffer<T> ringBuffer;
/**消费者事件处理线程池**/
private final Executor executor;
/**消费者集合**/
private final ConsumerRepository<T> consumerRepository = new ConsumerRepository<T>();
/**Disruptor是否启动标示,只能启动一次**/
private final AtomicBoolean started = new AtomicBoolean(false);
/**消费者事件异常处理方法**/
private ExceptionHandler<? super T> exceptionHandler = new ExceptionHandlerWrapper<T>();
实例化Disruptor的过程,就是实例化RingBuffer与消费线程池Executor的过程。除此之外,Disruptor类最重要的作用是注册消费者,handleEventsWith方法。该方法有多套实现,而每一个消费者最终都会被包装成EventProcessor。createEventProcessors是包装消费者的重要函数。
EventHandlerGroup<T> createEventProcessors(final Sequence[] barrierSequences,
                                           final EventHandler<T>[] eventHandlers)
{
	checkNotStarted();
	//每个消费者有自己的事件序号Sequence
	final Sequence[] processorSequences = new Sequence[eventHandlers.length];
	//消费者通过SequenceBarrier等待可消费事件
	final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);   	for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++)
	{
		final EventHandler<T> eventHandler = eventHandlers[i];
		//每个消费者都以BatchEventProcessor被调度
		final BatchEventProcessor<T> batchEventProcessor = new BatchEventProcessor<T>(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<T>(this, consumerRepository, processorSequences);
}
从程序中可以看出,每个消费者都以BatchEventProcessor的形式被调度,也就是说,消费者的逻辑都在BatchEventProcessor。
2.3 EventProcessor
EventProcessor有两个有操作逻辑的实现类,BatchEventProcessor与WorkProcessor,处理逻辑很相近,这边仅分析BatchEventProcessor。
BatchEventProcessor的构造函数使用DataProvider,而不直接使用RingBuffer,可能是Disruptor考虑到留给用户替换RingBuffer事件存储的空间,毕竟RingBuffer是内存级的。
Disruptor启动时,会调用每个消费者ConsumerInfo(在消费者集合ConsumerRepository中)的start方法,最终会运行到BatchEventProcessor的run方法。
@Override
public void run()
{
	if (!running.compareAndSet(false, true))
	{
		throw new IllegalStateException("Thread is already running");
	}
	sequenceBarrier.clearAlert();
	notifyStart();
	T event = null;
	// sequence.get()标示当前已经处理的序号
	long nextSequence = sequence.get() + 1L;
	try
	{
		while (true)
		{
			try
			{
				// sequenceBarrier最重要的作用,就是让消费者等待下一个可用的序号
				// 可用序号可能会大于nextSequence,从而消费者可以一次处理多个事件
				// 如果该消费者同时也依赖了其他消费者,则会返回最小的那个
				final long availableSequence = sequenceBarrier.waitFor(nextSequence);
				if (nextSequence > availableSequence)
				{
					Thread.yield();
				}
				while (nextSequence <= availableSequence)
				{
					event = dataProvider.get(nextSequence);
					// eventHandler是用户定义的事件消费逻辑
					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.handleEventException(ex, nextSequence, event);
				sequence.set(nextSequence);
				nextSequence++;
			}
		}
	}
	finally
	{
		notifyShutdown();
		running.set(false);
	}
}
消费者的逻辑,就是在while循环中,不断查询可消费事件,并由用户自定义的消费逻辑eventHandler进行处理。查询可消费事件的逻辑在SequenceBarrier中。
2.4 SequenceBarrier
SequenceBarrier只有一个实现,ProcessingSequenceBarrier。下面是ProcessingSequenceBarrier的构造函数。
public ProcessingSequenceBarrier(final Sequencer sequencer,final WaitStrategy waitStrategy,final Sequence cursorSequence,final Sequence[] dependentSequences)
{
	// 生产者的ringBuffer控制器sequencer
	this.sequencer = sequencer;
	// 消费者等待可消费事件的策略
	this.waitStrategy = waitStrategy;
	// ringBuffer的cursor
	this.cursorSequence = cursorSequence;
	if (0 == dependentSequences.length)
	{
		dependentSequence = cursorSequence;
	}
	else
	{
	// 当依赖其他消费者时,dependentSequence就是其他消费者的序号
		dependentSequence = new FixedSequenceGroup(dependentSequences);
	}
}
消费者通过ProcessingSequenceBarrier的waitFor方法等待可消费序号,实际是调用WaitStrategy的waitFor方法。
2.5 WaitStrategy
WaitStrategy有6个实现类,用于代表6种不同的等待策略,比如阻塞策略、忙等策略等。这边就仅分析一个阻塞策略BlockingWaitStrategy。
@Override
public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier)
	throws AlertException, InterruptedException
{
	long availableSequence;
	if ((availableSequence = cursorSequence.get()) < sequence)
	{
		lock.lock();
		try
		{
			// 如果ringBuffer的cursor小于需要的序号,也就是生产者没有新的事件发出,则阻塞消费者线程,直到生产者通过Sequencer的publish方法唤醒消费者。
			while ((availableSequence = cursorSequence.get()) < sequence)
			{
				barrier.checkAlert();
				processorNotifyCondition.await();
			}
		}
		finally
		{
			lock.unlock();
		}
	}
	// 如果生产者新发布了事件,但是依赖的其他消费者还没处理完,则等待所依赖的消费者先处理。在本文的例子中,就是等B与C先处理完,D才能处理事件。
	while ((availableSequence = dependentSequence.get()) < sequence)
	{
		barrier.checkAlert();
	}
	return availableSequence;
}
到这里,消费者的程序逻辑也就基本都清楚了。最后再看一下生产者的程序逻辑,主要是Sequencer。
2.6 Sequencer
Sequencer负责生产者对RingBuffer的控制,包括查询是否有写入空间、申请空间、发布事件并唤醒消费者等。Sequencer有两个实现SingleProducerSequencer与MultiProducerSequencer,分别对应于单生产者模型与多生产者模型。只要看懂hasAvailableCapacity(),申请空间也就明白了。下面是SingleProducerSequencer的hasAvailableCapacity实现。
@Override
public boolean hasAvailableCapacity(final int requiredCapacity)
{
	long nextValue = pad.nextValue;
	// wrapPoint是一个临界序号,必须比当前最小的未消费序号还小
	long wrapPoint = (nextValue + requiredCapacity) - bufferSize;
	// 当前的最小未消费序号
	long cachedGatingSequence = pad.cachedValue;
	if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
	{
		long minSequence = Util.getMinimumSequence(gatingSequences, nextValue);
		pad.cachedValue = minSequence;
		if (wrapPoint > minSequence)
		{
			return false;
		}
	}
	return true;
}
3 Disruptor实例
本实例基于3.2.0版本的Disruptor,实现2.1小结描述的并发场景。使用Disruptor的过程非常简单,只需要简单的几步。
定义用户事件:
public class MyEvent {
	private long value;
	public MyEvent(){}
	public long getValue() {
		return value;
	}
	public void setValue(long value) {
		this.value = value;
	}
}
定义事件工厂,这是实例化Disruptor所需要的:
public class MyEventFactory implements EventFactory<MyEvent> {
	public MyEvent newInstance() {
		return new MyEvent();
	}
}
定义消费者B、C、D:
public class MyEventHandlerB implements EventHandler<MyEvent> {
	public void onEvent(MyEvent myEvent, long l, boolean b) throws Exception {
		System.out.println("Comsume Event B : " + myEvent.getValue());
	}
}
public class MyEventHandlerC implements EventHandler<MyEvent> {
	public void onEvent(MyEvent myEvent, long l, boolean b) throws Exception {
		System.out.println("Comsume Event C : " + myEvent.getValue());
	}
}
public class MyEventHandlerD implements EventHandler<MyEvent> {
	public void onEvent(MyEvent myEvent, long l, boolean b) throws Exception {
		System.out.println("Comsume Event D : " + myEvent.getValue());
	}
}
在此基础上,就可以运行Disruptor了:
public static void main(String[] args){
	EventFactory<MyEvent> myEventFactory = new MyEventFactory();
	Executor executor = Executors.newCachedThreadPool();
	int ringBufferSize = 32;
	Disruptor<MyEvent> disruptor = new Disruptor<MyEvent>(myEventFactory,ringBufferSize,executor, ProducerType.SINGLE,new BlockingWaitStrategy());
	EventHandler<MyEvent> b = new MyEventHandlerB();
	EventHandler<MyEvent> c = new MyEventHandlerC();
	EventHandler<MyEvent> d = new MyEventHandlerD();
	SequenceBarrier sequenceBarrier2 = disruptor.handleEventsWith(b,c).asSequenceBarrier();
	BatchEventProcessor processord = new BatchEventProcessor(disruptor.getRingBuffer(),sequenceBarrier2,d);
	disruptor.handleEventsWith(processord);
//  disruptor.after(b,c).handleEventsWith(d);              // 此行能代替上两行的程序逻辑
	RingBuffer<MyEvent> ringBuffer = disruptor.start();    // 启动Disruptor
	for(int i=0; i<10; i++) {
		long sequence = ringBuffer.next();                 // 申请位置
		try {
			MyEvent myEvent = ringBuffer.get(sequence);
			myEvent.setValue(i);                           // 放置数据
		} finally {
			ringBuffer.publish(sequence);                  // 提交,如果不提交完成事件会一直阻塞
		}
		try{
			Thread.sleep(100);
		}catch (Exception e){
		}
	}
	disruptor.shutdown();
}
按照程序的逻辑,B与C会率先处理ringBuffer中的事件,且处理顺序不分先后。同一事件被B与C处理完成之后,才会被D处理,结果如下:
Comsume Event C : 0
Comsume Event B : 0
Comsume Event D : 0
Comsume Event C : 1
Comsume Event B : 1
Comsume Event D : 1
Comsume Event C : 2
Comsume Event B : 2
Comsume Event D : 2
Comsume Event C : 3
Comsume Event B : 3
Comsume Event D : 3
Comsume Event C : 4
Comsume Event B : 4
Comsume Event D : 4
Comsume Event C : 5
Comsume Event B : 5
Comsume Event D : 5
Comsume Event C : 6
Comsume Event B : 6
Comsume Event D : 6
Comsume Event C : 7
Comsume Event B : 7
Comsume Event D : 7
Comsume Event C : 8
Comsume Event B : 8
Comsume Event D : 8
Comsume Event C : 9
Comsume Event B : 9
Comsume Event D : 9
将本例中的Thread.sleep去掉,即可以观察到B与C的处理不分先后,结果符合预期。
本文乃作者原创,转载请注明出处。http://www.cnblogs.com/miao-rui/p/6379473.html
Disruptor深入解读的更多相关文章
- Disruptor源码解读
		
上一篇已经介绍了Disruptor是什么?简单总结了为什么这么快?下面我们直接源码搞起来,简单粗暴.高性能队列disruptor为什么这么快? 一.核心类接口 Disruptor 提供了对RingBu ...
 - Disruptor 源码阅读笔记--转
		
原文地址:http://coderbee.net/index.php/open-source/20130812/400 一.Disruptor 是什么? Disruptor 是一个高性能异步处理框架, ...
 - Disruptor学习笔记
		
前言 以前一直听说有Disruptor这个东西,都说性能很强大,所以这几天自己也看了一下. 下面是自己的学习笔记,另外推荐几篇自己看到写的比较好的博客: Disruptor——一种可替代有界队列完成并 ...
 - log4j2异步日志解读(二)AsyncLogger
		
前文已经讲了log4j2的AsyncAppender的实现[log4j2异步日志解读(一)AsyncAppender],今天我们看看AsyncLogger的实现. 看了这个图,应该很清楚AsyncLo ...
 - 高性能队列disruptor为什么这么快?
		
背景 Disruptor是LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级).基于Disruptor开发的系统单线程能支撑每秒600万 ...
 - 高性能环形队列框架 Disruptor 核心概念
		
高性能环形队列框架 Disruptor Disruptor 是英国外汇交易公司LMAX开发的一款高吞吐低延迟内存队列框架,其充分考虑了底层CPU等运行模式来进行数据结构设计 (mechanical s ...
 - 一文读懂原子操作、内存屏障、锁(偏向锁、轻量级锁、重量级锁、自旋锁)、Disruptor、Go Context之上半部分
		
我不想卷,我是被逼的 在做了几年前端之后,发现互联网行情比想象的差,不如赶紧学点后端知识,被裁之后也可接个私活不至于饿死.学习两周Go,如盲人摸象般不知重点,那么重点谁知道呢?肯定是使用Go的后端工程 ...
 - SDWebImage源码解读之SDWebImageDownloaderOperation
		
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
 - SDWebImage源码解读 之 NSData+ImageContentType
		
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
 
随机推荐
- iOS navigationBar 的isTranslucent属性
			
苹果文档: A Boolean value indicating whether the navigation bar is translucent (YES) or not (NO). The de ...
 - sublime text 我的常用配置
			
{ "color_scheme": "Packages/Color Scheme - Default/IDLE.tmTheme", "font_fac ...
 - AdaBoost的java实现
			
目前学了几个ML的分类的经典算法,但是一直想着是否有一种能将这些算法集成起来的,今天看到了AdaBoost,也算是半个集成,感觉这个思路挺好,很像人的训练过程,并且对决策树是一个很好的补充,因为决策树 ...
 - C socket post数据到url
			
#define HOST_SERVER_IP "192.168.1.15" #define HOST_PORT 80 int gsh_post_clients(const char ...
 - iOS给自定义个model排序
			
今天有朋友问我怎么给Model排序,我顺便写了一个,伸手党直接复制吧. 例如,我建了一个Person类,要按Person的年龄属性排序: Person *per = [[Person alloc] i ...
 - FZU	2098 刻苦的小芳
			
这个问题转化一下就是求长度为2*n的正确括号匹配串,两个匹配的括号之间的距离不超过2*k的有几种. 假设左括号为1,右括号为-1,dp[i][j]表示长度为i的括号匹配串,前缀和为j的有几种.dp[2 ...
 - tp框架中的静态验证
			
//制定命名空间在Home 模块下Model文件夹下 如:namespace Home\Model; //引用父类 如:use Think\Model; //实例化表 如:class ZhuCeMod ...
 - S3C2440的SPI解析
			
位串行数据的频率.如果只希望发送,则接收数据可以保持伪位(dummy).此外如果只希望接收,则需要发送伪位'1'数据 使用SPI主要需要以下寄存器 选择SPI模式,中断模式,查询模式等SCK选择,主从 ...
 - iOS8 UISearchViewController搜索功能讲解
			
在iOS8以前我们实现搜索功能需要用到UISearchbar和UISearchDisplayController, 在iOS8之后呢, UISearchController配合UITableView的 ...
 - iOS纯代码手动适配                                                    分类:            ios技术             2015-05-04 17:14    239人阅读    评论(0)    收藏
			
首先说下让自己的程序支持iPhone6和6+,第一种使用官方提供的launch screen.xib,这个直接看官方文档即可,这里不再多述:第二种方法是和之前iPhone5的类似,比较简单,为iPho ...