一、简介
Disruptor是一个生产者/消费者模式的框架,它有一个名字叫RingBuffer的容器。生产者生产数据,往RingBuffer上面装;消费者从RingBuffer上取数据,就像JDK中concurrent包的BlockingQueue一样,可以同时存取。但是Disruptor采用了无锁机制、批量消费等一系列优化策略,比BlockingQueue性能更优。至于它可以做什么,那就有很多场合了,在当今数据量庞大的时代,数据无非是从生产者流向消费者,而Disruptor正好可以作为生产者与消费者的中间者,成为他们之间的桥梁。
二、一个简单的disruptor程序
1、核心组件
在这里,我们先介绍一下这个简单程所用到的核心组件。
- RingBuffer:它是一个环形的数据结构,容量由用户自己定义,一般为2^n。它实际上是一段连续内存,之所以称作环形是因为它对数据存放位置的处理,生产者和消费者各有一个指针,消费者的指针指向下一个要读取的Slot,生产者指针指向下一个要放入的Slot,消费或生产后,各自的指针值p = (p +1) % n,n是缓冲区长度(RingBuffer容量),这样指针在缓冲区上反复游走,故可以将缓冲区看成环状。如下图所示:
- EventFactory:Event是我们要传递的数据,它不用实现任何接口,只是一个普通的bean类。EventFactory就是生产数据的工厂。在Disruptor中,并不是每个数据都要在虚拟机中新建,而是使用已经读取过的数据对象,重新set里面的属性,减少GC次数。EventFactory就承担了创建对象的职责。
- EventHandler:EventHandler相当于消费者,它的实现类里面封装了相应的处理。
- Sequence: Disruptor使用Sequence来表示一个特殊组件处理的序号。和Disruptor一样,每个消费者(EventProcessor)都维持着一个Sequence。大部分的并发代码依赖这些Sequence值的运转,因此Sequence支持多种当前为AtomicLong类的特性。生产者所获取的下一个可写sequence就是通过它来维护的。
2、简单的Disruptor程序
(1)、数据类
public class LongEvent {
private long data;
public long getData() {
return data;
}
public void setData(long data) {
this.data = data;
}
}
数据类没什么特别,只是一个简单的bean类,跟ORM框架中的实体类差不多,只不过ORM的实体类用于持久化;而这里的数据用于传递,是生产者/消费者间传递的实体。
(2)、数据工厂
public class LongEventFactory implements EventFactory<LongEvent> {
public LongEvent newInstance() {
return new LongEvent();
}
}
数据工厂类实现了EventFactory接口,用于生产数据。前面我们说到Disruptor中数据对象是可以重复使用的,并不是每个数据都需要创建一个对象,只需要用set设置已经不会再使用的对象即可。而数据工厂则是对象的创建者。
3、消费者
public class Consumer implements EventHandler<LongEvent> {
public void onEvent(LongEvent longEvent, long l, boolean b) throws Exception {
System.out.println(data);
}
}
在消费者中,我们可以对数据进行相关处理,这里只是简单的读取。如果有多个消费者的话,上一个消费者做了处理的数据会传给下一个消费者,其实质上操作的是同一个Entry(键值对对象)。
4、生产者
public class Producer {
public static void main(String[] args) throws TimeoutException {
//(1)、创建线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
//(2)、新建Disruptor对象
Disruptor<LongEvent> disruptor = new Disruptor<LongEvent>(new LongEventFactory(),
8, executor, ProducerType.MULTI, new BlockingWaitStrategy());
//(3)、在Disruptor中设置消费者
disruptor.handleEventsWith(new Consumer());
//(4)、启动Disruptor
disruptor.start();
//(5)、获取RingBuffer
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
//(6)、获取下一个可写的slot
long sequence = ringBuffer.next();
LongEvent event = ringBuffer.get(sequence);
event.setData(1);
//(7)、往RingBuffer中推送数据
ringBuffer.publish(sequence);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
disruptor.shutdown();
executor.shutdown();
}
}
这里我们简单地将生产者放在main方法中,其实他还可以开启多条线程进行生产,而且不用加锁。因为RingBuffer中只维护一个写指针,如果多个线程竞争,则可通过CAS来保证同步。
3、多个消费者配合消费
在Disruptor中,还支持这么一个场景,假设有三个消费者A、B、C,A处理数据对象中的一部分属性,B处理数据对象中的另一部分属性,然后在对象C中显示A、B处理后的效果接下来,我们来模拟一下。
在这里,我们的数据对象和数据工厂跟上面代码大同小异,只是将数据换成了Person(里面有Id、name属性以及相应的getter/setter方法),数据工厂为PersonFactory。消费者分成了3个,ConsumerA设置Person的Id,ConsumerB设置Person的name,ConsumerC显示Person的Id,name。接下来,我们对Disruptor的调用代码进行展示。
关键点在上图红色框框处,handleEventsWith()方法接收消费者的可变参数,返回一个EventHandlerGroup对象,作为可变参数传进去的消费者同时处理内容,相当于下图的C1、C2。EventHandlerGroup的then()方法也传进一个消费者对象,他相当于下图的C3,等C1和C2处理完后再对数据进行处理。
三、实现消费者的另外两种模式
1、EventProcessor
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//创建RingBuffer,用于装数据
RingBuffer<LongEvent> ringBuffer = RingBuffer.createSingleProducer(
new LongEventFactory(), 1024, new BlockingWaitStrategy());
//创建消费者,这是一个Runnable的实现类
BatchEventProcessor<LongEvent> processor = new BatchEventProcessor<LongEvent>(
ringBuffer, ringBuffer.newBarrier(), new Consumer());
//每个消费者维护一个Sequence,将它添加到RingBuffer中
ringBuffer.addGatingSequences(processor.getSequence());
//将消费者提交到线程池中执行
service.submit(processor);
//生产数据
for(int i=0; i<10; i++){
long seq = ringBuffer.next();
ringBuffer.get(seq).setData(i);
ringBuffer.publish(seq);
}
processor.halt();
service.shutdown();
}
从上述代码中更明晰了一些问题——Disruptor中的核心元素。首先是线程池,它用于执行消费者的任务;接下来是Sequence,每个消费者内部都有一个Sequence,来维护数据读取顺序;RingBuffer更不用说,它和生产者紧密相连,谁往里面填数据,谁就是生产者。
2、WorkHandler
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//创建RingBuffer,用于装数据
RingBuffer<LongEvent> ringBuffer = RingBuffer.createSingleProducer(
new LongEventFactory(), 1024, new BlockingWaitStrategy());
WorkerPool<LongEvent> pool = new WorkerPool<LongEvent>(
ringBuffer, ringBuffer.newBarrier(), new IgnoreExceptionHandler(), new Consumer());
//用线程池执行消费者的任务
pool.start(service);
//生产数据
for(int i=0; i<10; i++){
long seq = ringBuffer.next();
ringBuffer.get(seq).setData(i);
ringBuffer.publish(seq);
}
pool.halt();
service.shutdown();
}
这里的消费者有些不一样,要实现WorkHandler接口。从上述代码可以看出,EventProcessor和WorkHandler作为消费者的使用方式都相差无几。同样每个生产者都要有Sequence来维护内部数据读取顺序,同样需要RingBuffer装载数据以及王RingBuffer中填充数据,同样需要线程池来执行消费者逻辑。
四、总结
从上述说明中,我们可以总结出几个核心点。RingBuffer是一个环形数据结构,用于装载数据;生产者往RingBuffer中填充数据;消费者的逻辑在线程池中执行;数据对象通过EventFactory来生产,生产出足够对象后以后的数据操作都只是操作里面的属性,而不用再新建那么多对象。