Disruptor学习总结(一):Disruptor的使用

一、简介

      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来生产,生产出足够对象后以后的数据操作都只是操作里面的属性,而不用再新建那么多对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值