每天一例多线程[day22]-----Disruptor并发框架简介HelloWorld

Disruptor是一个开源的并发框架,并获得2011年Duke's程序框架创新奖,能够在无锁的情况下实现网络的Queue队列并发操作。Disruptor是一个高性能的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式的实现,或者事件监听模式的实现。也可以理解为一个高效的“生产者-消费者”模型。性能远远高于传统的BlockingQueue。


Disruptor的Ringbuffer模型:


分析:disruptor内部维护的Ringbuffer其实结构是一个首尾相接的环形,用作在多线程之间传递数据的buffer,ringbuffer有一个序号sequence,这个序号sequence指向数组中下一个可用的元素。随着生产者不停填充这个ringbuffer,这个序号sequence可能会一直增长,直到绕过这个环。如上图,生产者向一个容量为8的ringbuffer环中填充了8个数据从1到8,当sequence为0的元素1被消费者消费掉后,生产者就将下一个元素放置到原来存放1的sequence为0的位置上,但是此时的sequence不是0,而是接着增长到了9,这是因为ringbuffer维护的是一个AutomicLong的数值来表示sequence。

只要知道了sequence序号,就可以通过取模的方式得到元素在ringbuffer环中的位置:

                                       sequence mod array length = arrayIndex

以上边图中的ringbuffer为例,如果sequence=8,那么8%8=0,这样消费者就可以通过下标0拿到ringbuffer环数组的元素。

注意:上图中ringbuffer只有8个槽只是个例子,如果槽的个数是2的N次方,则会更有利于二进制计算机计算。


理解RingBuffer:

1 没有尾指针:Disruptor的ringbuffer没有尾指针,只维护了一个指向下一个可用位置的序号sequence,这种实现是设计者经过深思熟虑的,设计之初就是为了提供可靠的消息传递。

2 不需要删除元素:RingBuffer和常用队列区别是,我们不能删除RingBuffer中的数据,也就是说这些数据一直存放在RingBuffer中,直到新的数据覆盖它们,这也是之所以不需要尾指针的原因,因为根本不需要控制数据的覆盖。

3 RingBuffer是数组,所以比链表查询速度快,而且有一个可以预测的访问模式。

数组是对CPU友好的,在硬件层面,数组的元素是会被预加载的,因此在RingBuffer中,cpu不需要时不时的加载数据中下一个元素。


Disruptor的术语说明

WaitStrategy:决定消费者如何等待生产者将Event放入Disruptor。

Event:从生产者到消费者所处理的数据单元。Disruptor中没有代码定义Event,因此它完全是由用户来定义的。

EventProcessor:主要事件循环,处理Disruptor中的Event,并且拥有消费者的sequence。它有一个实现类是BatchEventProcessor,包含了Event loop有效的实现,并且将回调到一个EventHandler的接口实现对象。

EventHandler:由用户实现并且代表了Disruptor中的一个消费者的接口。

Producer:事件生产者,由用户实现,它调用Ringbuffer来插入事件Event,在Disruptor没有相应实现代码,由用户实现。

WorkProcessor:确保每一个sequence只被一个Processor消费,在同一个workPool中处理多个workProcessor不会消费同样的sequence。

WorkerPool:一个workProcessor池,其中workProcessor将消费Sequence,所以任务可以在实现WorkHandler接口的worker之间移交。

LifeCycleAware:由BatchEventProcessor启动和停止时实现这个接口用于接收通知。


HelloWorld代码实现:

LongEvent事件对象

//http://ifeve.com/disruptor-getting-started/

/**
 * 生产者要生产的【event对象】,主动传递给disruptor中的RingBuffer
 * @author jeffSheng
 *
 */
public class LongEvent { 
	
    private long value;
    
    public long getValue() { 
        return value; 
    } 
 
    public void setValue(long value) { 
        this.value = value; 
    } 
} 

LongEventFactory事件工厂

import com.lmax.disruptor.EventFactory;
/**
 * 实现disruptor的【事件工厂】EventFactory,让disruptor批量产生longEvent
 * @author jeffSheng
 *
 */
public class LongEventFactory implements EventFactory { 

    @Override 
    public Object newInstance() { 
        return new LongEvent(); 
    } 
} 

LongEventHandler事件处理器( 消费者)

/**
 * 定义disruptor的【消费者】
 * 我们还需要一个事件消费者实现disruptor的EventHandler,也就是一个事件处理器。
 * 这个事件处理器简单地把事件中存储的数据打印到终端:
 * @author jeffSheng
 *
 */
public class LongEventHandler implements EventHandler<LongEvent>  {

	@Override
	public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
		System.out.println(longEvent.getValue()); 		
	}

}

主函数:

import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;

public class LongEventMain {

	public static void main(String[] args) throws Exception {
		//创建缓冲线程池
		ExecutorService  executor = Executors.newCachedThreadPool();
		//创建LongEvent事件工厂
		LongEventFactory factory = new LongEventFactory();
		//创建bufferSize缓冲区 ,也就是RingBuffer大小,要求必须是2的N次方
		int ringBufferSize = 1024 * 1024; 

		
		/**
		 * 创建disruptor实例,并传入泛型LongEvent事件类型(数据类型)
		 * 构造参数:
		 * Disruptor(
			 * factory,--事件工厂,用于创建LongEvent,也就是实际最终被消费的数据
			 * ringBufferSize,--缓冲区大小
			 * executor,--线程池,作用是使用线程池进行内部数据接收处理调度
			 * producerType,--两种形式:SINGLE(生产者只有一个)和MULTI(生产者有多个)
			 * waitStrategy--决定一个消费者将如何等待生产者将EVENT放入disruptor
		 * )
				 * 	//BlockingWaitStrategy 是最低效的策略,但其对CPU的消耗最小,并且在各种不同部署环境中能提供更加一致的性能表现
					WaitStrategy BLOCKING_WAIT = new BlockingWaitStrategy();
					//SleepingWaitStrategy 的性能表现跟BlockingWaitStrategy差不多,对CPU的消耗也类似,但其对生产者线程的影响最小,适合用于异步日志类似的场景
					WaitStrategy SLEEPING_WAIT = new SleepingWaitStrategy();
					//YieldingWaitStrategy 的性能是最好的,适合用于低延迟的系统。在要求极高性能且事件处理线数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性
					WaitStrategy YIELDING_WAIT = new YieldingWaitStrategy();
		 */
		Disruptor<LongEvent> disruptor = 
				new Disruptor<LongEvent>(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
		
		/**
		 * 连接消费事件方法,监听ringbuffer环形队列容器中的事件,有则取出消费,无则阻塞,所以disruptor相当于一个特殊的有界阻塞队列
		 * LongEventHandler 理解为数据消费者
		 */
		disruptor.handleEventsWith(new LongEventHandler());
		// 启动disruptor
		disruptor.start();
		
		//Disruptor 的事件发布过程是一个两阶段提交的过程:
		/**
		 * 发布事件
		 * 使用该方法获得具体存放数据的容器ringBuffer(环形结构)
		 */
		RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
		//定义事件生产者
		LongEventProducer producer = new LongEventProducer(ringBuffer); 
//		LongEventProducerWithTranslator producer = new LongEventProducerWithTranslator(ringBuffer);
		//定义一个含有8个空间的字节缓冲
		ByteBuffer byteBuffer = ByteBuffer.allocate(8);
		for(long a = 0; a < 100; a++){
			byteBuffer.putLong(0, a);//每次覆盖byteBuffer下标为0的位置
			//producer将数据byteBuffer存入ringbuffer事件槽
			producer.onData(byteBuffer);
			//Thread.sleep(1000);
		}

		
		disruptor.shutdown();//关闭 disruptor,方法会堵塞,直至所有的事件都得到处理;
		executor.shutdown();//关闭 disruptor 使用的线程池;如果需要的话,必须手动关闭, disruptor 在 shutdown 时不会自动关闭;		
		
		
		
		
		
		
		
	}
}

主函数代码分析

实现HelloWorld的步骤:

第一,建立Event类

第二,建立一个Event工厂类,用户实例化Event对象。

第三,需要一个事件监听器,用于处理Event类

以上三步:

//创建缓冲线程池
		ExecutorService  executor = Executors.newCachedThreadPool();
		//创建LongEvent事件工厂
		LongEventFactory factory = new LongEventFactory();
		//创建bufferSize缓冲区 ,也就是RingBuffer大小,要求必须是2的N次方
		int ringBufferSize = 1024 * 1024; 

第四,编写主函数,实例化Disruptor实例,传入构造参数,然后disruptor绑定监听事件类,接收并处理数据。


 创建disruptor实例,并传入泛型LongEvent事件类型(数据类型)
 构造参数:
new  Disruptor(
 factory,--事件工厂,用于创建LongEvent,也就是实际最终被消费的数据
 ringBufferSize,--缓冲区大小
 executor,--线程池,作用是使用线程池进行内部数据接收处理调度
 producerType,--两种形式:SINGLE(生产者只有一个)和MULTI(生产者有多个)
 waitStrategy--决定一个消费者将如何等待生产者将EVENT放入disruptor
  )

	Disruptor<LongEvent> disruptor = 
				new Disruptor<LongEvent>(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
		
		/**
		 * 连接消费事件方法,监听ringbuffer环形队列容器中的事件,有则取出消费,无则阻塞,所以disruptor相当于一个特殊的有界阻塞队列
		 * LongEventHandler 理解为数据消费者
		 */
		disruptor.handleEventsWith(new LongEventHandler());
		// 启动disruptor
		disruptor.start();

第五,在disruptor中,真正存储数据的核心是Ringbuffer,我们通过disruptor实例拿到了它,然后把数据生产了出来,把数据加入到Ringbuffer中即可。

	         //定义事件生产者
		LongEventProducer producer = new LongEventProducer(ringBuffer); 
                //定义一个含有8个空间的字节缓冲
		ByteBuffer byteBuffer = ByteBuffer.allocate(8);
		for(long a = 0; a < 100; a++){
			byteBuffer.putLong(0, a);//每次覆盖byteBuffer下标为0的位置
			//producer将数据byteBuffer存入ringbuffer事件槽
			producer.onData(byteBuffer);
			//Thread.sleep(1000);
		}
		disruptor.shutdown();//关闭 disruptor,方法会堵塞,直至所有的事件都得到处理;
		executor.shutdown();//关闭 disruptor 使用的线程池;如果需要的话,必须手动关闭, disruptor 在 shutdown 时不会自动关闭;	

生产者定义方式一:

import java.nio.ByteBuffer;
import com.lmax.disruptor.RingBuffer;

public class LongEventProducer {

	private final RingBuffer<LongEvent> ringBuffer;
	
	public LongEventProducer(RingBuffer<LongEvent> ringBuffer){
		this.ringBuffer = ringBuffer;
	}
	
	/**
	 * onData用来发布事件,每调用一次就发布一次事件, 它的参数bb会通过事件传递给消费者
	 * 将bb存入ringBuffer环形事件容器
	 * 生产数据需要遵循的四个步骤
	 */
	public void onData(ByteBuffer bb){
		//1.可以把ringBuffer看做一个环形事件队列,那么next就是得到下面一个空的事件槽索引
		long sequence = ringBuffer.next();
		try {
			//2.用上面的索引取出一个空的事件用于填充(获取该序号对应的事件对象)
			LongEvent event = ringBuffer.get(sequence);
			//3.获取要通过事件传递的业务数据
			event.setValue(bb.getLong(0));
		} finally {
			//4.发布事件
			/**
			 * 注意,最后的 ringBuffer.publish 方法必须包含在 finally 中以确保必须得到调用;
			 * 如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
			 * 只有发布后才能被监听的消费者消费掉
			 */
			ringBuffer.publish(sequence);
		}
	}
}


生产者定义方式二:

import java.nio.ByteBuffer;
import com.lmax.disruptor.EventTranslatorOneArg;
import com.lmax.disruptor.RingBuffer;

/**
 * Disruptor 3.0提供了lambda式的API。这样可以把一些复杂的操作放在Ring Buffer,
 * 所以在Disruptor3.0以后的版本最好使用Event Publisher或者Event Translator来发布事件
 */
public class LongEventProducerWithTranslator {

	//一个translator可以看做一个事件初始化器,publicEvent方法会调用它
	//填充Event
	private static final EventTranslatorOneArg<LongEvent, ByteBuffer> TRANSLATOR = 
			new EventTranslatorOneArg<LongEvent, ByteBuffer>() {
				@Override
				public void translateTo(LongEvent event, long sequeue, ByteBuffer buffer) {
					event.setValue(buffer.getLong(0));
				}
			};
	
	private final RingBuffer<LongEvent> ringBuffer;
	
	public LongEventProducerWithTranslator(RingBuffer<LongEvent> ringBuffer) {
		this.ringBuffer = ringBuffer;
	}
	
	public void onData(ByteBuffer buffer){
		ringBuffer.publishEvent(TRANSLATOR, buffer);
	}
}

结果打印:控制台从0打印到99.




猜你喜欢

转载自blog.csdn.net/shengqianfeng/article/details/80673119
今日推荐