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.